blob: 9b9d6e6d487740526b158a0619736ec256c55252 [file] [log] [blame]
Daniel Sandler325dc232013-06-05 22:57:57 -04001package com.android.launcher3;
Adam Cohend4844c32011-02-18 19:25:06 -08002
3import android.animation.AnimatorSet;
4import android.animation.ObjectAnimator;
5import android.animation.PropertyValuesHolder;
6import android.animation.ValueAnimator;
7import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -08008import android.appwidget.AppWidgetHostView;
Adam Cohend4844c32011-02-18 19:25:06 -08009import android.appwidget.AppWidgetProviderInfo;
10import android.content.Context;
Adam Cohen59400422014-03-05 18:07:04 -080011import android.content.res.Resources;
Sunny Goyal756cd262015-08-20 12:33:21 -070012import android.graphics.Point;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -080013import android.graphics.Rect;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070014import android.util.AttributeSet;
Tony Wickham71255bb2016-02-10 16:18:15 -080015import android.view.KeyEvent;
Sunny Goyal6ad72f02016-09-23 11:01:10 -070016import android.view.MotionEvent;
Tony Wickham71255bb2016-02-10 16:18:15 -080017import android.view.View;
Adam Cohend4844c32011-02-18 19:25:06 -080018import android.widget.FrameLayout;
Adam Cohend4844c32011-02-18 19:25:06 -080019
Sunny Goyale78e3d72015-09-24 11:23:31 -070020import com.android.launcher3.accessibility.DragViewStateAnnouncer;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070021import com.android.launcher3.dragndrop.DragLayer;
Tony Wickham71255bb2016-02-10 16:18:15 -080022import com.android.launcher3.util.FocusLogic;
Sunny Goyal6ad72f02016-09-23 11:01:10 -070023import com.android.launcher3.util.TouchController;
Sunny Goyale78e3d72015-09-24 11:23:31 -070024
Sunny Goyal6ad72f02016-09-23 11:01:10 -070025public class AppWidgetResizeFrame extends FrameLayout
26 implements View.OnKeyListener, TouchController {
Sunny Goyalba776d52015-05-18 20:52:57 -070027 private static final int SNAP_DURATION = 150;
28 private static final float DIMMED_HANDLE_ALPHA = 0f;
29 private static final float RESIZE_THRESHOLD = 0.66f;
30
Sunny Goyal756cd262015-08-20 12:33:21 -070031 private static final Rect sTmpRect = new Rect();
32
33 // Represents the cell size on the grid in the two orientations.
34 private static Point[] sCellSize;
Sunny Goyalba776d52015-05-18 20:52:57 -070035
Sunny Goyal08ca40f2016-09-13 21:07:31 -070036 private static final int HANDLE_COUNT = 4;
37 private static final int INDEX_LEFT = 0;
38 private static final int INDEX_TOP = 1;
39 private static final int INDEX_RIGHT = 2;
40 private static final int INDEX_BOTTOM = 3;
41
Sunny Goyalba776d52015-05-18 20:52:57 -070042 private final Launcher mLauncher;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070043 private final DragViewStateAnnouncer mStateAnnouncer;
Sunny Goyalba776d52015-05-18 20:52:57 -070044
Sunny Goyal08ca40f2016-09-13 21:07:31 -070045 private final View[] mDragHandles = new View[HANDLE_COUNT];
Sunny Goyalba776d52015-05-18 20:52:57 -070046
Sunny Goyal08ca40f2016-09-13 21:07:31 -070047 private LauncherAppWidgetHostView mWidgetView;
48 private CellLayout mCellLayout;
49 private DragLayer mDragLayer;
50
51 private Rect mWidgetPadding;
Sunny Goyalba776d52015-05-18 20:52:57 -070052
53 private final int mBackgroundPadding;
54 private final int mTouchTargetWidth;
55
56 private final int[] mDirectionVector = new int[2];
57 private final int[] mLastDirectionVector = new int[2];
58 private final int[] mTmpPt = new int[2];
Adam Cohend4844c32011-02-18 19:25:06 -080059
Sunny Goyal08ca40f2016-09-13 21:07:31 -070060 private final IntRange mTempRange1 = new IntRange();
61 private final IntRange mTempRange2 = new IntRange();
62
63 private final IntRange mDeltaXRange = new IntRange();
64 private final IntRange mBaselineX = new IntRange();
65
66 private final IntRange mDeltaYRange = new IntRange();
67 private final IntRange mBaselineY = new IntRange();
Sunny Goyale78e3d72015-09-24 11:23:31 -070068
Adam Cohend4844c32011-02-18 19:25:06 -080069 private boolean mLeftBorderActive;
70 private boolean mRightBorderActive;
71 private boolean mTopBorderActive;
72 private boolean mBottomBorderActive;
73
Adam Cohend4844c32011-02-18 19:25:06 -080074 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080075
Adam Cohend4844c32011-02-18 19:25:06 -080076 private int mRunningHInc;
77 private int mRunningVInc;
78 private int mMinHSpan;
79 private int mMinVSpan;
80 private int mDeltaX;
81 private int mDeltaY;
Adam Cohenbebf0422012-04-11 18:06:28 -070082 private int mDeltaXAddOn;
83 private int mDeltaYAddOn;
Adam Cohen1b607ed2011-03-03 17:26:50 -080084
Adam Cohen4459d6b2012-07-13 15:59:15 -070085 private int mTopTouchRegionAdjustment = 0;
86 private int mBottomTouchRegionAdjustment = 0;
87
Sunny Goyal6ad72f02016-09-23 11:01:10 -070088 private int mXDown, mYDown;
89
Sunny Goyal08ca40f2016-09-13 21:07:31 -070090 public AppWidgetResizeFrame(Context context) {
91 this(context, null);
92 }
Adam Cohend4844c32011-02-18 19:25:06 -080093
Sunny Goyal08ca40f2016-09-13 21:07:31 -070094 public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
95 this(context, attrs, 0);
96 }
97
98 public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
99 super(context, attrs, defStyleAttr);
100
101 mLauncher = Launcher.getLauncher(context);
102 mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
103
104 mBackgroundPadding = getResources()
105 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
106 mTouchTargetWidth = 2 * mBackgroundPadding;
107 }
108
109 @Override
110 protected void onFinishInflate() {
111 super.onFinishInflate();
112
113 for (int i = 0; i < HANDLE_COUNT; i ++) {
114 mDragHandles[i] = getChildAt(i);
115 }
116 }
117
118 public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
119 DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -0800120 mCellLayout = cellLayout;
121 mWidgetView = widgetView;
Adam Cohen59400422014-03-05 18:07:04 -0800122 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
123 widgetView.getAppWidgetInfo();
124 mResizeMode = info.resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -0800125 mDragLayer = dragLayer;
Adam Cohen3cba7222011-03-02 19:03:11 -0800126
Sunny Goyal233ee962015-08-03 13:05:01 -0700127 mMinHSpan = info.minSpanX;
128 mMinVSpan = info.minSpanY;
Adam Cohend4844c32011-02-18 19:25:06 -0800129
Adam Cohen59400422014-03-05 18:07:04 -0800130 if (!info.isCustomWidget) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700131 mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
Adam Cohen59400422014-03-05 18:07:04 -0800132 widgetView.getAppWidgetInfo().provider, null);
133 } else {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700134 Resources r = getContext().getResources();
Adam Cohen59400422014-03-05 18:07:04 -0800135 int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
Sunny Goyalba776d52015-05-18 20:52:57 -0700136 mWidgetPadding = new Rect(padding, padding, padding, padding);
Adam Cohen59400422014-03-05 18:07:04 -0800137 }
138
Adam Cohend4844c32011-02-18 19:25:06 -0800139 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700140 mDragHandles[INDEX_TOP].setVisibility(GONE);
141 mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
Adam Cohend4844c32011-02-18 19:25:06 -0800142 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700143 mDragHandles[INDEX_LEFT].setVisibility(GONE);
144 mDragHandles[INDEX_RIGHT].setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800145 }
146
Adam Cohenbebf0422012-04-11 18:06:28 -0700147 // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
148 // cells (same if not resized, or different) will be marked as occupied when the resize
149 // frame is dismissed.
150 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
Tony Wickham71255bb2016-02-10 16:18:15 -0800151
152 setOnKeyListener(this);
Adam Cohend4844c32011-02-18 19:25:06 -0800153 }
154
155 public boolean beginResizeIfPointInRegion(int x, int y) {
156 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
157 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700158
Adam Cohen3cba7222011-03-02 19:03:11 -0800159 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
160 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700161 mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
162 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
163 && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800164
165 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
166 || mTopBorderActive || mBottomBorderActive;
167
Adam Cohend4844c32011-02-18 19:25:06 -0800168 if (anyBordersActive) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700169 mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
170 mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
171 mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
172 mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800173 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700174
175 if (mLeftBorderActive) {
176 mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
177 } else if (mRightBorderActive) {
178 mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
179 } else {
180 mDeltaXRange.set(0, 0);
181 }
182 mBaselineX.set(getLeft(), getRight());
183
184 if (mTopBorderActive) {
185 mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
186 } else if (mBottomBorderActive) {
187 mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
188 } else {
189 mDeltaYRange.set(0, 0);
190 }
191 mBaselineY.set(getTop(), getBottom());
192
Adam Cohend4844c32011-02-18 19:25:06 -0800193 return anyBordersActive;
194 }
195
Adam Cohen1b607ed2011-03-03 17:26:50 -0800196 /**
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700197 * Based on the deltas, we resize the frame.
Adam Cohen1b607ed2011-03-03 17:26:50 -0800198 */
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700199 public void visualizeResizeForDelta(int deltaX, int deltaY) {
200 mDeltaX = mDeltaXRange.clamp(deltaX);
201 mDeltaY = mDeltaYRange.clamp(deltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800202
Adam Cohen67882692011-03-11 15:29:03 -0800203 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700204 mDeltaX = mDeltaXRange.clamp(deltaX);
205 mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
206 lp.x = mTempRange1.start;
207 lp.width = mTempRange1.size();
Adam Cohen67882692011-03-11 15:29:03 -0800208
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700209 mDeltaY = mDeltaYRange.clamp(deltaY);
210 mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
211 lp.y = mTempRange1.start;
212 lp.height = mTempRange1.size();
Adam Cohend4844c32011-02-18 19:25:06 -0800213
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700214 resizeWidgetIfNeeded(false);
Jon Miranda097a7252016-12-28 10:49:55 -0800215
216 // When the widget resizes in multi-window mode, the translation value changes to maintain
217 // a center fit. These overrides ensure the resize frame always aligns with the widget view.
218 getSnappedRectRelativeToDragLayer(sTmpRect);
219 if (mLeftBorderActive) {
220 lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
221 }
222 if (mTopBorderActive) {
223 lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
224 }
225 if (mRightBorderActive) {
226 lp.x = sTmpRect.left;
227 }
228 if (mBottomBorderActive) {
229 lp.y = sTmpRect.top;
230 }
231
Adam Cohend4844c32011-02-18 19:25:06 -0800232 requestLayout();
233 }
234
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700235 private static int getSpanIncrement(float deltaFrac) {
236 return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
237 }
238
Adam Cohen1b607ed2011-03-03 17:26:50 -0800239 /**
240 * Based on the current deltas, we determine if and how to resize the widget.
241 */
Adam Cohenbebf0422012-04-11 18:06:28 -0700242 private void resizeWidgetIfNeeded(boolean onDismiss) {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530243 float xThreshold = mCellLayout.getCellWidth();
244 float yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800245
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700246 int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
247 int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
Adam Cohene4b77292011-03-08 18:35:52 -0800248
Adam Cohenbebf0422012-04-11 18:06:28 -0700249 if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800250
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700251 mDirectionVector[0] = 0;
252 mDirectionVector[1] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800253
254 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800255
Adam Cohenbebf0422012-04-11 18:06:28 -0700256 int spanX = lp.cellHSpan;
257 int spanY = lp.cellVSpan;
258 int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
259 int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
260
Adam Cohen1b607ed2011-03-03 17:26:50 -0800261 // For each border, we bound the resizing based on the minimum width, and the maximum
262 // expandability.
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700263 mTempRange1.set(cellX, spanX + cellX);
264 int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
265 hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
266 cellX = mTempRange2.start;
267 spanX = mTempRange2.size();
268 if (hSpanDelta != 0) {
269 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800270 }
271
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700272 mTempRange1.set(cellY, spanY + cellY);
273 int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
274 vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
275 cellY = mTempRange2.start;
276 spanY = mTempRange2.size();
277 if (vSpanDelta != 0) {
278 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800279 }
280
Adam Cohenbebf0422012-04-11 18:06:28 -0700281 if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800282
Adam Cohene0489502012-08-27 15:18:53 -0700283 // We always want the final commit to match the feedback, so we make sure to use the
284 // last used direction vector when committing the resize / reorder.
285 if (onDismiss) {
286 mDirectionVector[0] = mLastDirectionVector[0];
287 mDirectionVector[1] = mLastDirectionVector[1];
288 } else {
289 mLastDirectionVector[0] = mDirectionVector[0];
290 mLastDirectionVector[1] = mDirectionVector[1];
291 }
292
Adam Cohenbebf0422012-04-11 18:06:28 -0700293 if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
294 mDirectionVector, onDismiss)) {
Sunny Goyale78e3d72015-09-24 11:23:31 -0700295 if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
296 mStateAnnouncer.announce(
297 mLauncher.getString(R.string.widget_resized, spanX, spanY));
298 }
299
Adam Cohenbebf0422012-04-11 18:06:28 -0700300 lp.tmpCellX = cellX;
301 lp.tmpCellY = cellY;
302 lp.cellHSpan = spanX;
303 lp.cellVSpan = spanY;
304 mRunningVInc += vSpanDelta;
305 mRunningHInc += hSpanDelta;
Sunny Goyale78e3d72015-09-24 11:23:31 -0700306
Adam Cohena897f392012-04-27 18:12:05 -0700307 if (!onDismiss) {
308 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
309 }
Adam Cohenbebf0422012-04-11 18:06:28 -0700310 }
Adam Cohen67882692011-03-11 15:29:03 -0800311 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800312 }
313
Adam Cohena897f392012-04-27 18:12:05 -0700314 static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
315 int spanX, int spanY) {
Sunny Goyalba776d52015-05-18 20:52:57 -0700316 getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
317 widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
318 sTmpRect.right, sTmpRect.bottom);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700319 }
320
Sunny Goyal16466f12016-03-10 05:34:30 -0800321 public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
Sunny Goyal756cd262015-08-20 12:33:21 -0700322 if (sCellSize == null) {
323 InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile();
324
325 // Initiate cell sizes.
326 sCellSize = new Point[2];
327 sCellSize[0] = inv.landscapeProfile.getCellSize();
328 sCellSize[1] = inv.portraitProfile.getCellSize();
329 }
330
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700331 if (rect == null) {
332 rect = new Rect();
333 }
Sunny Goyal16466f12016-03-10 05:34:30 -0800334 final float density = context.getResources().getDisplayMetrics().density;
Adam Cohena897f392012-04-27 18:12:05 -0700335
336 // Compute landscape size
Sunny Goyal756cd262015-08-20 12:33:21 -0700337 int landWidth = (int) ((spanX * sCellSize[0].x) / density);
338 int landHeight = (int) ((spanY * sCellSize[0].y) / density);
Adam Cohena897f392012-04-27 18:12:05 -0700339
340 // Compute portrait size
Sunny Goyal756cd262015-08-20 12:33:21 -0700341 int portWidth = (int) ((spanX * sCellSize[1].x) / density);
342 int portHeight = (int) ((spanY * sCellSize[1].y) / density);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700343 rect.set(portWidth, landHeight, landWidth, portHeight);
344 return rect;
Adam Cohena897f392012-04-27 18:12:05 -0700345 }
346
Adam Cohen1b607ed2011-03-03 17:26:50 -0800347 /**
348 * This is the final step of the resize. Here we save the new widget size and position
349 * to LauncherModel and animate the resize frame.
350 */
Adam Cohenbebf0422012-04-11 18:06:28 -0700351 public void commitResize() {
352 resizeWidgetIfNeeded(true);
353 requestLayout();
354 }
Adam Cohend4844c32011-02-18 19:25:06 -0800355
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700356 private void onTouchUp() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530357 int xThreshold = mCellLayout.getCellWidth();
358 int yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800359
Jon Miranda097a7252016-12-28 10:49:55 -0800360 mDeltaXAddOn = mRunningHInc * xThreshold;
361 mDeltaYAddOn = mRunningVInc * yThreshold;
Adam Cohenbebf0422012-04-11 18:06:28 -0700362 mDeltaX = 0;
363 mDeltaY = 0;
364
Adam Cohend4844c32011-02-18 19:25:06 -0800365 post(new Runnable() {
Adam Cohenbebf0422012-04-11 18:06:28 -0700366 @Override
Adam Cohend4844c32011-02-18 19:25:06 -0800367 public void run() {
368 snapToWidget(true);
369 }
370 });
371 }
372
Jon Miranda097a7252016-12-28 10:49:55 -0800373 /**
374 * Returns the rect of this view when the frame is snapped around the widget, with the bounds
375 * relative to the {@link DragLayer}.
376 */
377 private void getSnappedRectRelativeToDragLayer(Rect out) {
Jon Miranda21266912016-12-19 14:12:05 -0800378 float scale = mWidgetView.getScaleToFit();
Adam Cohen37b59ff2011-06-13 17:13:42 -0700379
Jon Miranda097a7252016-12-28 10:49:55 -0800380 mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
Adam Cohend6e7aa32013-07-09 15:32:37 -0700381
Jon Miranda097a7252016-12-28 10:49:55 -0800382 int width = 2 * mBackgroundPadding
383 + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
384 int height = 2 * mBackgroundPadding
385 + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
Jon Miranda26e4d172016-12-12 15:43:18 -0800386
Jon Miranda097a7252016-12-28 10:49:55 -0800387 int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
388 int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
389
390 out.left = x;
391 out.top = y;
392 out.right = out.left + width;
393 out.bottom = out.top + height;
394 }
395
396 public void snapToWidget(boolean animate) {
397 getSnappedRectRelativeToDragLayer(sTmpRect);
398 int newWidth = sTmpRect.width();
399 int newHeight = sTmpRect.height();
400 int newX = sTmpRect.left;
401 int newY = sTmpRect.top;
Adam Cohen3cba7222011-03-02 19:03:11 -0800402
Sunny Goyalba776d52015-05-18 20:52:57 -0700403 // We need to make sure the frame's touchable regions lie fully within the bounds of the
Adam Cohen4459d6b2012-07-13 15:59:15 -0700404 // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
405 // down accordingly to provide a proper touch target.
Adam Cohen3cba7222011-03-02 19:03:11 -0800406 if (newY < 0) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700407 // In this case we shift the touch region down to start at the top of the DragLayer
408 mTopTouchRegionAdjustment = -newY;
409 } else {
410 mTopTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800411 }
Adam Cohen67882692011-03-11 15:29:03 -0800412 if (newY + newHeight > mDragLayer.getHeight()) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700413 // In this case we shift the touch region up to end at the bottom of the DragLayer
414 mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
415 } else {
416 mBottomTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800417 }
418
Jon Miranda26e4d172016-12-12 15:43:18 -0800419 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -0800420 if (!animate) {
421 lp.width = newWidth;
422 lp.height = newHeight;
423 lp.x = newX;
424 lp.y = newY;
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700425 for (int i = 0; i < HANDLE_COUNT; i++) {
426 mDragHandles[i].setAlpha(1.0f);
427 }
Adam Cohend4844c32011-02-18 19:25:06 -0800428 requestLayout();
429 } else {
430 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800431 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
432 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800433 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
434 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100435 ObjectAnimator oa =
436 LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
Adam Cohend4844c32011-02-18 19:25:06 -0800437 oa.addUpdateListener(new AnimatorUpdateListener() {
438 public void onAnimationUpdate(ValueAnimator animation) {
439 requestLayout();
440 }
441 });
Michael Jurka2ecf9952012-06-18 12:52:28 -0700442 AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700443 set.play(oa);
444 for (int i = 0; i < HANDLE_COUNT; i++) {
445 set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
Adam Cohend4844c32011-02-18 19:25:06 -0800446 }
447
448 set.setDuration(SNAP_DURATION);
449 set.start();
450 }
Tony Wickham71255bb2016-02-10 16:18:15 -0800451
452 setFocusableInTouchMode(true);
453 requestFocus();
454 }
455
456 @Override
457 public boolean onKey(View v, int keyCode, KeyEvent event) {
458 // Clear the frame and give focus to the widget host view when a directional key is pressed.
459 if (FocusLogic.shouldConsume(keyCode)) {
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700460 mDragLayer.clearResizeFrame();
Tony Wickham71255bb2016-02-10 16:18:15 -0800461 mWidgetView.requestFocus();
462 return true;
463 }
464 return false;
Adam Cohend4844c32011-02-18 19:25:06 -0800465 }
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700466
467 private boolean handleTouchDown(MotionEvent ev) {
468 Rect hitRect = new Rect();
469 int x = (int) ev.getX();
470 int y = (int) ev.getY();
471
472 getHitRect(hitRect);
473 if (hitRect.contains(x, y)) {
474 if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
475 mXDown = x;
476 mYDown = y;
477 return true;
478 }
479 }
480 return false;
481 }
482
483 @Override
484 public boolean onControllerTouchEvent(MotionEvent ev) {
485 int action = ev.getAction();
486 int x = (int) ev.getX();
487 int y = (int) ev.getY();
488
489 switch (action) {
490 case MotionEvent.ACTION_DOWN:
491 return handleTouchDown(ev);
492 case MotionEvent.ACTION_MOVE:
493 visualizeResizeForDelta(x - mXDown, y - mYDown);
494 break;
495 case MotionEvent.ACTION_CANCEL:
496 case MotionEvent.ACTION_UP:
497 visualizeResizeForDelta(x - mXDown, y - mYDown);
498 onTouchUp();
499 mXDown = mYDown = 0;
500 break;
501 }
502 return true;
503 }
504
505 @Override
506 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
507 if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
508 return true;
509 }
510 return false;
511 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700512
513 /**
514 * A mutable class for describing the range of two int values.
515 */
516 private static class IntRange {
517
518 public int start, end;
519
520 public int clamp(int value) {
521 return Utilities.boundToRange(value, start, end);
522 }
523
524 public void set(int s, int e) {
525 start = s;
526 end = e;
527 }
528
529 public int size() {
530 return end - start;
531 }
532
533 /**
534 * Moves either the start or end edge (but never both) by {@param delta} and sets the
535 * result in {@param out}
536 */
537 public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
538 out.start = moveStart ? start + delta : start;
539 out.end = moveEnd ? end + delta : end;
540 }
541
542 /**
543 * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
544 * with extra conditions.
545 * @param minSize minimum size after with the moving edge should not be shifted any further.
546 * For eg, if delta = -3 when moving the endEdge brings the size to less than
547 * minSize, only delta = -2 will applied
548 * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
549 * @return the amount of increase when endEdge was moves and the amount of decrease when
550 * the start edge was moved.
551 */
552 public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
553 int minSize, int maxEnd, IntRange out) {
554 applyDelta(moveStart, moveEnd, delta, out);
555 if (start < 0) {
556 out.start = 0;
557 }
558 if (end > maxEnd) {
559 out.end = maxEnd;
560 }
561 if (out.size() < minSize) {
562 if (moveStart) {
563 out.start = out.end - minSize;
564 } else if (moveEnd) {
565 out.end = out.start + minSize;
566 }
567 }
568 return moveEnd ? out.size() - size() : size() - out.size();
569 }
570 }
Adam Cohend4844c32011-02-18 19:25:06 -0800571}