Refactoring AppWidgetResizeFrame

> Defining the layout in xml
> Simplifying the touch handling calculations

Change-Id: Iccfd82161d1e678d77ad6ff63f76e04ad905f9d8
This commit is contained in:
Sunny Goyal
2016-09-13 21:07:31 -07:00
parent 5fe414f9a4
commit 08ca40f976
3 changed files with 246 additions and 204 deletions
@@ -1,7 +1,5 @@
package com.android.launcher3;
import com.android.launcher3.dragndrop.DragLayer;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -13,14 +11,14 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.Gravity;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.FocusLogic;
import com.android.launcher3.util.TouchController;
@@ -35,17 +33,22 @@ public class AppWidgetResizeFrame extends FrameLayout
// Represents the cell size on the grid in the two orientations.
private static Point[] sCellSize;
private static final int HANDLE_COUNT = 4;
private static final int INDEX_LEFT = 0;
private static final int INDEX_TOP = 1;
private static final int INDEX_RIGHT = 2;
private static final int INDEX_BOTTOM = 3;
private final Launcher mLauncher;
private final LauncherAppWidgetHostView mWidgetView;
private final CellLayout mCellLayout;
private final DragLayer mDragLayer;
private final DragViewStateAnnouncer mStateAnnouncer;
private final ImageView mLeftHandle;
private final ImageView mRightHandle;
private final ImageView mTopHandle;
private final ImageView mBottomHandle;
private final View[] mDragHandles = new View[HANDLE_COUNT];
private final Rect mWidgetPadding;
private LauncherAppWidgetHostView mWidgetView;
private CellLayout mCellLayout;
private DragLayer mDragLayer;
private Rect mWidgetPadding;
private final int mBackgroundPadding;
private final int mTouchTargetWidth;
@@ -54,17 +57,20 @@ public class AppWidgetResizeFrame extends FrameLayout
private final int[] mLastDirectionVector = new int[2];
private final int[] mTmpPt = new int[2];
private final DragViewStateAnnouncer mStateAnnouncer;
private final IntRange mTempRange1 = new IntRange();
private final IntRange mTempRange2 = new IntRange();
private final IntRange mDeltaXRange = new IntRange();
private final IntRange mBaselineX = new IntRange();
private final IntRange mDeltaYRange = new IntRange();
private final IntRange mBaselineY = new IntRange();
private boolean mLeftBorderActive;
private boolean mRightBorderActive;
private boolean mTopBorderActive;
private boolean mBottomBorderActive;
private int mBaselineWidth;
private int mBaselineHeight;
private int mBaselineX;
private int mBaselineY;
private int mResizeMode;
private int mRunningHInc;
@@ -81,11 +87,36 @@ public class AppWidgetResizeFrame extends FrameLayout
private int mXDown, mYDown;
public AppWidgetResizeFrame(Context context,
LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
public AppWidgetResizeFrame(Context context) {
this(context, null);
}
super(context);
mLauncher = (Launcher) context;
public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
mBackgroundPadding = getResources()
.getDimensionPixelSize(R.dimen.resize_frame_background_padding);
mTouchTargetWidth = 2 * mBackgroundPadding;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
for (int i = 0; i < HANDLE_COUNT; i ++) {
mDragHandles[i] = getChildAt(i);
}
}
public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
DragLayer dragLayer) {
mCellLayout = cellLayout;
mWidgetView = widgetView;
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
@@ -96,63 +127,23 @@ public class AppWidgetResizeFrame extends FrameLayout
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
setBackgroundResource(R.drawable.widget_resize_shadow);
setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
setPadding(0, 0, 0, 0);
final int handleMargin = getResources().getDimensionPixelSize(R.dimen.widget_handle_margin);
LayoutParams lp;
mLeftHandle = new ImageView(context);
mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
Gravity.LEFT | Gravity.CENTER_VERTICAL);
lp.leftMargin = handleMargin;
addView(mLeftHandle, lp);
mRightHandle = new ImageView(context);
mRightHandle.setImageResource(R.drawable.ic_widget_resize_handle);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
Gravity.RIGHT | Gravity.CENTER_VERTICAL);
lp.rightMargin = handleMargin;
addView(mRightHandle, lp);
mTopHandle = new ImageView(context);
mTopHandle.setImageResource(R.drawable.ic_widget_resize_handle);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
Gravity.CENTER_HORIZONTAL | Gravity.TOP);
lp.topMargin = handleMargin;
addView(mTopHandle, lp);
mBottomHandle = new ImageView(context);
mBottomHandle.setImageResource(R.drawable.ic_widget_resize_handle);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
lp.bottomMargin = handleMargin;
addView(mBottomHandle, lp);
if (!info.isCustomWidget) {
mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context,
mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
widgetView.getAppWidgetInfo().provider, null);
} else {
Resources r = context.getResources();
Resources r = getContext().getResources();
int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
mWidgetPadding = new Rect(padding, padding, padding, padding);
}
if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
mTopHandle.setVisibility(GONE);
mBottomHandle.setVisibility(GONE);
mDragHandles[INDEX_TOP].setVisibility(GONE);
mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
} else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
mLeftHandle.setVisibility(GONE);
mRightHandle.setVisibility(GONE);
mDragHandles[INDEX_LEFT].setVisibility(GONE);
mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
mBackgroundPadding = getResources()
.getDimensionPixelSize(R.dimen.resize_frame_background_padding);
mTouchTargetWidth = 2 * mBackgroundPadding;
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
@@ -174,101 +165,74 @@ public class AppWidgetResizeFrame extends FrameLayout
boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
|| mTopBorderActive || mBottomBorderActive;
mBaselineWidth = getMeasuredWidth();
mBaselineHeight = getMeasuredHeight();
mBaselineX = getLeft();
mBaselineY = getTop();
if (anyBordersActive) {
mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
}
if (mLeftBorderActive) {
mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
} else if (mRightBorderActive) {
mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
} else {
mDeltaXRange.set(0, 0);
}
mBaselineX.set(getLeft(), getRight());
if (mTopBorderActive) {
mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
} else if (mBottomBorderActive) {
mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
} else {
mDeltaYRange.set(0, 0);
}
mBaselineY.set(getTop(), getBottom());
return anyBordersActive;
}
/**
* Here we bound the deltas such that the frame cannot be stretched beyond the extents
* of the CellLayout, and such that the frame's borders can't cross.
* Based on the deltas, we resize the frame.
*/
public void updateDeltas(int deltaX, int deltaY) {
if (mLeftBorderActive) {
mDeltaX = Math.max(-mBaselineX, deltaX);
mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
} else if (mRightBorderActive) {
mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
}
public void visualizeResizeForDelta(int deltaX, int deltaY) {
mDeltaX = mDeltaXRange.clamp(deltaX);
mDeltaY = mDeltaYRange.clamp(deltaY);
if (mTopBorderActive) {
mDeltaY = Math.max(-mBaselineY, deltaY);
mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
} else if (mBottomBorderActive) {
mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
}
}
private void visualizeResizeForDelta(int deltaX, int deltaY) {
visualizeResizeForDelta(deltaX, deltaY, false);
}
/**
* Based on the deltas, we resize the frame, and, if needed, we resize the widget.
*/
private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
updateDeltas(deltaX, deltaY);
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
mDeltaX = mDeltaXRange.clamp(deltaX);
mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
lp.x = mTempRange1.start;
lp.width = mTempRange1.size();
if (mLeftBorderActive) {
lp.x = mBaselineX + mDeltaX;
lp.width = mBaselineWidth - mDeltaX;
} else if (mRightBorderActive) {
lp.width = mBaselineWidth + mDeltaX;
}
mDeltaY = mDeltaYRange.clamp(deltaY);
mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
lp.y = mTempRange1.start;
lp.height = mTempRange1.size();
if (mTopBorderActive) {
lp.y = mBaselineY + mDeltaY;
lp.height = mBaselineHeight - mDeltaY;
} else if (mBottomBorderActive) {
lp.height = mBaselineHeight + mDeltaY;
}
resizeWidgetIfNeeded(onDismiss);
resizeWidgetIfNeeded(false);
requestLayout();
}
private static int getSpanIncrement(float deltaFrac) {
return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
}
/**
* Based on the current deltas, we determine if and how to resize the widget.
*/
private void resizeWidgetIfNeeded(boolean onDismiss) {
int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
float xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
float yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
int deltaX = mDeltaX + mDeltaXAddOn;
int deltaY = mDeltaY + mDeltaYAddOn;
float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
int hSpanInc = 0;
int vSpanInc = 0;
int cellXInc = 0;
int cellYInc = 0;
int countX = mCellLayout.getCountX();
int countY = mCellLayout.getCountY();
if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
hSpanInc = Math.round(hSpanIncF);
}
if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
vSpanInc = Math.round(vSpanIncF);
}
int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
mDirectionVector[0] = 0;
mDirectionVector[1] = 0;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
@@ -277,55 +241,24 @@ public class AppWidgetResizeFrame extends FrameLayout
int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
int hSpanDelta = 0;
int vSpanDelta = 0;
// For each border, we bound the resizing based on the minimum width, and the maximum
// expandability.
if (mLeftBorderActive) {
cellXInc = Math.max(-cellX, hSpanInc);
cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
hSpanInc *= -1;
hSpanInc = Math.min(cellX, hSpanInc);
hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
hSpanDelta = -hSpanInc;
} else if (mRightBorderActive) {
hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
hSpanDelta = hSpanInc;
mTempRange1.set(cellX, spanX + cellX);
int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
cellX = mTempRange2.start;
spanX = mTempRange2.size();
if (hSpanDelta != 0) {
mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
if (mTopBorderActive) {
cellYInc = Math.max(-cellY, vSpanInc);
cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
vSpanInc *= -1;
vSpanInc = Math.min(cellY, vSpanInc);
vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
vSpanDelta = -vSpanInc;
} else if (mBottomBorderActive) {
vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
vSpanDelta = vSpanInc;
}
mDirectionVector[0] = 0;
mDirectionVector[1] = 0;
// Update the widget's dimensions and position according to the deltas computed above
if (mLeftBorderActive || mRightBorderActive) {
spanX += hSpanInc;
cellX += cellXInc;
if (hSpanDelta != 0) {
mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
}
if (mTopBorderActive || mBottomBorderActive) {
spanY += vSpanInc;
cellY += cellYInc;
if (vSpanDelta != 0) {
mDirectionVector[1] = mTopBorderActive ? -1 : 1;
}
mTempRange1.set(cellY, spanY + cellY);
int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
cellY = mTempRange2.start;
spanY = mTempRange2.size();
if (vSpanDelta != 0) {
mDirectionVector[1] = mTopBorderActive ? -1 : 1;
}
if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
@@ -455,10 +388,9 @@ public class AppWidgetResizeFrame extends FrameLayout
lp.height = newHeight;
lp.x = newX;
lp.y = newY;
mLeftHandle.setAlpha(1.0f);
mRightHandle.setAlpha(1.0f);
mTopHandle.setAlpha(1.0f);
mBottomHandle.setAlpha(1.0f);
for (int i = 0; i < HANDLE_COUNT; i++) {
mDragHandles[i].setAlpha(1.0f);
}
requestLayout();
} else {
PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
@@ -468,22 +400,15 @@ public class AppWidgetResizeFrame extends FrameLayout
PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
ObjectAnimator oa =
LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f);
ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f);
ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f);
ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f);
oa.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
requestLayout();
}
});
AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
set.playTogether(oa, topOa, bottomOa);
} else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
set.playTogether(oa, leftOa, rightOa);
} else {
set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
set.play(oa);
for (int i = 0; i < HANDLE_COUNT; i++) {
set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
}
set.setDuration(SNAP_DURATION);
@@ -550,4 +475,63 @@ public class AppWidgetResizeFrame extends FrameLayout
}
return false;
}
/**
* A mutable class for describing the range of two int values.
*/
private static class IntRange {
public int start, end;
public int clamp(int value) {
return Utilities.boundToRange(value, start, end);
}
public void set(int s, int e) {
start = s;
end = e;
}
public int size() {
return end - start;
}
/**
* Moves either the start or end edge (but never both) by {@param delta} and sets the
* result in {@param out}
*/
public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
out.start = moveStart ? start + delta : start;
out.end = moveEnd ? end + delta : end;
}
/**
* Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
* with extra conditions.
* @param minSize minimum size after with the moving edge should not be shifted any further.
* For eg, if delta = -3 when moving the endEdge brings the size to less than
* minSize, only delta = -2 will applied
* @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
* @return the amount of increase when endEdge was moves and the amount of decrease when
* the start edge was moved.
*/
public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
int minSize, int maxEnd, IntRange out) {
applyDelta(moveStart, moveEnd, delta, out);
if (start < 0) {
out.start = 0;
}
if (end > maxEnd) {
out.end = maxEnd;
}
if (out.size() < minSize) {
if (moveStart) {
out.start = out.end - minSize;
} else if (moveEnd) {
out.end = out.start + minSize;
}
}
return moveEnd ? out.size() - size() : size() - out.size();
}
}
}