• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher3;
2 
3 import android.animation.AnimatorSet;
4 import android.animation.ObjectAnimator;
5 import android.animation.PropertyValuesHolder;
6 import android.animation.ValueAnimator;
7 import android.animation.ValueAnimator.AnimatorUpdateListener;
8 import android.appwidget.AppWidgetHostView;
9 import android.appwidget.AppWidgetProviderInfo;
10 import android.content.Context;
11 import android.graphics.Rect;
12 import android.view.Gravity;
13 import android.widget.FrameLayout;
14 import android.widget.ImageView;
15 
16 public class AppWidgetResizeFrame extends FrameLayout {
17     private LauncherAppWidgetHostView mWidgetView;
18     private CellLayout mCellLayout;
19     private DragLayer mDragLayer;
20     private ImageView mLeftHandle;
21     private ImageView mRightHandle;
22     private ImageView mTopHandle;
23     private ImageView mBottomHandle;
24 
25     private boolean mLeftBorderActive;
26     private boolean mRightBorderActive;
27     private boolean mTopBorderActive;
28     private boolean mBottomBorderActive;
29 
30     private int mWidgetPaddingLeft;
31     private int mWidgetPaddingRight;
32     private int mWidgetPaddingTop;
33     private int mWidgetPaddingBottom;
34 
35     private int mBaselineWidth;
36     private int mBaselineHeight;
37     private int mBaselineX;
38     private int mBaselineY;
39     private int mResizeMode;
40 
41     private int mRunningHInc;
42     private int mRunningVInc;
43     private int mMinHSpan;
44     private int mMinVSpan;
45     private int mDeltaX;
46     private int mDeltaY;
47     private int mDeltaXAddOn;
48     private int mDeltaYAddOn;
49 
50     private int mBackgroundPadding;
51     private int mTouchTargetWidth;
52 
53     private int mTopTouchRegionAdjustment = 0;
54     private int mBottomTouchRegionAdjustment = 0;
55 
56     int[] mDirectionVector = new int[2];
57     int[] mLastDirectionVector = new int[2];
58     int[] mTmpPt = new int[2];
59 
60     final int SNAP_DURATION = 150;
61     final int BACKGROUND_PADDING = 24;
62     final float DIMMED_HANDLE_ALPHA = 0f;
63     final float RESIZE_THRESHOLD = 0.66f;
64 
65     private static Rect mTmpRect = new Rect();
66 
67     public static final int LEFT = 0;
68     public static final int TOP = 1;
69     public static final int RIGHT = 2;
70     public static final int BOTTOM = 3;
71 
72     private Launcher mLauncher;
73 
AppWidgetResizeFrame(Context context, LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer)74     public AppWidgetResizeFrame(Context context,
75             LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
76 
77         super(context);
78         mLauncher = (Launcher) context;
79         mCellLayout = cellLayout;
80         mWidgetView = widgetView;
81         mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
82         mDragLayer = dragLayer;
83 
84         final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
85         int[] result = Launcher.getMinSpanForWidget(mLauncher, info);
86         mMinHSpan = result[0];
87         mMinVSpan = result[1];
88 
89         setBackgroundResource(R.drawable.widget_resize_frame_holo);
90         setPadding(0, 0, 0, 0);
91 
92         LayoutParams lp;
93         mLeftHandle = new ImageView(context);
94         mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
95         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
96                 Gravity.LEFT | Gravity.CENTER_VERTICAL);
97         addView(mLeftHandle, lp);
98 
99         mRightHandle = new ImageView(context);
100         mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
101         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
102                 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
103         addView(mRightHandle, lp);
104 
105         mTopHandle = new ImageView(context);
106         mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
107         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
108                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
109         addView(mTopHandle, lp);
110 
111         mBottomHandle = new ImageView(context);
112         mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
113         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
114                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
115         addView(mBottomHandle, lp);
116 
117         Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context,
118                 widgetView.getAppWidgetInfo().provider, null);
119         mWidgetPaddingLeft = p.left;
120         mWidgetPaddingTop = p.top;
121         mWidgetPaddingRight = p.right;
122         mWidgetPaddingBottom = p.bottom;
123 
124         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
125             mTopHandle.setVisibility(GONE);
126             mBottomHandle.setVisibility(GONE);
127         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
128             mLeftHandle.setVisibility(GONE);
129             mRightHandle.setVisibility(GONE);
130         }
131 
132         final float density = mLauncher.getResources().getDisplayMetrics().density;
133         mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
134         mTouchTargetWidth = 2 * mBackgroundPadding;
135 
136         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
137         // cells (same if not resized, or different) will be marked as occupied when the resize
138         // frame is dismissed.
139         mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
140     }
141 
beginResizeIfPointInRegion(int x, int y)142     public boolean beginResizeIfPointInRegion(int x, int y) {
143         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
144         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
145 
146         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
147         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
148         mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
149         mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
150                 && verticalActive;
151 
152         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
153                 || mTopBorderActive || mBottomBorderActive;
154 
155         mBaselineWidth = getMeasuredWidth();
156         mBaselineHeight = getMeasuredHeight();
157         mBaselineX = getLeft();
158         mBaselineY = getTop();
159 
160         if (anyBordersActive) {
161             mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
162             mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
163             mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
164             mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
165         }
166         return anyBordersActive;
167     }
168 
169     /**
170      *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
171      *  of the CellLayout, and such that the frame's borders can't cross.
172      */
updateDeltas(int deltaX, int deltaY)173     public void updateDeltas(int deltaX, int deltaY) {
174         if (mLeftBorderActive) {
175             mDeltaX = Math.max(-mBaselineX, deltaX);
176             mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
177         } else if (mRightBorderActive) {
178             mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
179             mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
180         }
181 
182         if (mTopBorderActive) {
183             mDeltaY = Math.max(-mBaselineY, deltaY);
184             mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
185         } else if (mBottomBorderActive) {
186             mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
187             mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
188         }
189     }
190 
visualizeResizeForDelta(int deltaX, int deltaY)191     public void visualizeResizeForDelta(int deltaX, int deltaY) {
192         visualizeResizeForDelta(deltaX, deltaY, false);
193     }
194 
195     /**
196      *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
197      */
visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss)198     private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
199         updateDeltas(deltaX, deltaY);
200         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
201 
202         if (mLeftBorderActive) {
203             lp.x = mBaselineX + mDeltaX;
204             lp.width = mBaselineWidth - mDeltaX;
205         } else if (mRightBorderActive) {
206             lp.width = mBaselineWidth + mDeltaX;
207         }
208 
209         if (mTopBorderActive) {
210             lp.y = mBaselineY + mDeltaY;
211             lp.height = mBaselineHeight - mDeltaY;
212         } else if (mBottomBorderActive) {
213             lp.height = mBaselineHeight + mDeltaY;
214         }
215 
216         resizeWidgetIfNeeded(onDismiss);
217         requestLayout();
218     }
219 
220     /**
221      *  Based on the current deltas, we determine if and how to resize the widget.
222      */
resizeWidgetIfNeeded(boolean onDismiss)223     private void resizeWidgetIfNeeded(boolean onDismiss) {
224         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
225         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
226 
227         int deltaX = mDeltaX + mDeltaXAddOn;
228         int deltaY = mDeltaY + mDeltaYAddOn;
229 
230         float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
231         float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
232 
233         int hSpanInc = 0;
234         int vSpanInc = 0;
235         int cellXInc = 0;
236         int cellYInc = 0;
237 
238         int countX = mCellLayout.getCountX();
239         int countY = mCellLayout.getCountY();
240 
241         if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
242             hSpanInc = Math.round(hSpanIncF);
243         }
244         if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
245             vSpanInc = Math.round(vSpanIncF);
246         }
247 
248         if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
249 
250 
251         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
252 
253         int spanX = lp.cellHSpan;
254         int spanY = lp.cellVSpan;
255         int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
256         int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
257 
258         int hSpanDelta = 0;
259         int vSpanDelta = 0;
260 
261         // For each border, we bound the resizing based on the minimum width, and the maximum
262         // expandability.
263         if (mLeftBorderActive) {
264             cellXInc = Math.max(-cellX, hSpanInc);
265             cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
266             hSpanInc *= -1;
267             hSpanInc = Math.min(cellX, hSpanInc);
268             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
269             hSpanDelta = -hSpanInc;
270 
271         } else if (mRightBorderActive) {
272             hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
273             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
274             hSpanDelta = hSpanInc;
275         }
276 
277         if (mTopBorderActive) {
278             cellYInc = Math.max(-cellY, vSpanInc);
279             cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
280             vSpanInc *= -1;
281             vSpanInc = Math.min(cellY, vSpanInc);
282             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
283             vSpanDelta = -vSpanInc;
284         } else if (mBottomBorderActive) {
285             vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
286             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
287             vSpanDelta = vSpanInc;
288         }
289 
290         mDirectionVector[0] = 0;
291         mDirectionVector[1] = 0;
292         // Update the widget's dimensions and position according to the deltas computed above
293         if (mLeftBorderActive || mRightBorderActive) {
294             spanX += hSpanInc;
295             cellX += cellXInc;
296             if (hSpanDelta != 0) {
297                 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
298             }
299         }
300 
301         if (mTopBorderActive || mBottomBorderActive) {
302             spanY += vSpanInc;
303             cellY += cellYInc;
304             if (vSpanDelta != 0) {
305                 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
306             }
307         }
308 
309         if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
310 
311         // We always want the final commit to match the feedback, so we make sure to use the
312         // last used direction vector when committing the resize / reorder.
313         if (onDismiss) {
314             mDirectionVector[0] = mLastDirectionVector[0];
315             mDirectionVector[1] = mLastDirectionVector[1];
316         } else {
317             mLastDirectionVector[0] = mDirectionVector[0];
318             mLastDirectionVector[1] = mDirectionVector[1];
319         }
320 
321         if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
322                 mDirectionVector, onDismiss)) {
323             lp.tmpCellX = cellX;
324             lp.tmpCellY = cellY;
325             lp.cellHSpan = spanX;
326             lp.cellVSpan = spanY;
327             mRunningVInc += vSpanDelta;
328             mRunningHInc += hSpanDelta;
329             if (!onDismiss) {
330                 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
331             }
332         }
333         mWidgetView.requestLayout();
334     }
335 
updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher, int spanX, int spanY)336     static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
337             int spanX, int spanY) {
338 
339         getWidgetSizeRanges(launcher, spanX, spanY, mTmpRect);
340         widgetView.updateAppWidgetSize(null, mTmpRect.left, mTmpRect.top,
341                 mTmpRect.right, mTmpRect.bottom);
342     }
343 
getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect)344     static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
345         if (rect == null) {
346             rect = new Rect();
347         }
348         Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE);
349         Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT);
350         final float density = launcher.getResources().getDisplayMetrics().density;
351 
352         // Compute landscape size
353         int cellWidth = landMetrics.left;
354         int cellHeight = landMetrics.top;
355         int widthGap = landMetrics.right;
356         int heightGap = landMetrics.bottom;
357         int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
358         int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
359 
360         // Compute portrait size
361         cellWidth = portMetrics.left;
362         cellHeight = portMetrics.top;
363         widthGap = portMetrics.right;
364         heightGap = portMetrics.bottom;
365         int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
366         int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
367         rect.set(portWidth, landHeight, landWidth, portHeight);
368         return rect;
369     }
370 
371     /**
372      * This is the final step of the resize. Here we save the new widget size and position
373      * to LauncherModel and animate the resize frame.
374      */
commitResize()375     public void commitResize() {
376         resizeWidgetIfNeeded(true);
377         requestLayout();
378     }
379 
onTouchUp()380     public void onTouchUp() {
381         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
382         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
383 
384         mDeltaXAddOn = mRunningHInc * xThreshold;
385         mDeltaYAddOn = mRunningVInc * yThreshold;
386         mDeltaX = 0;
387         mDeltaY = 0;
388 
389         post(new Runnable() {
390             @Override
391             public void run() {
392                 snapToWidget(true);
393             }
394         });
395     }
396 
snapToWidget(boolean animate)397     public void snapToWidget(boolean animate) {
398         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
399         int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
400                 mWidgetPaddingRight;
401         int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
402                 mWidgetPaddingBottom;
403 
404         mTmpPt[0] = mWidgetView.getLeft();
405         mTmpPt[1] = mWidgetView.getTop();
406         mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
407 
408         int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPaddingLeft;
409         int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPaddingTop;
410 
411         // We need to make sure the frame's touchable regions lie fully within the bounds of the
412         // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
413         // down accordingly to provide a proper touch target.
414         if (newY < 0) {
415             // In this case we shift the touch region down to start at the top of the DragLayer
416             mTopTouchRegionAdjustment = -newY;
417         } else {
418             mTopTouchRegionAdjustment = 0;
419         }
420         if (newY + newHeight > mDragLayer.getHeight()) {
421             // In this case we shift the touch region up to end at the bottom of the DragLayer
422             mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
423         } else {
424             mBottomTouchRegionAdjustment = 0;
425         }
426 
427         if (!animate) {
428             lp.width = newWidth;
429             lp.height = newHeight;
430             lp.x = newX;
431             lp.y = newY;
432             mLeftHandle.setAlpha(1.0f);
433             mRightHandle.setAlpha(1.0f);
434             mTopHandle.setAlpha(1.0f);
435             mBottomHandle.setAlpha(1.0f);
436             requestLayout();
437         } else {
438             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
439             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
440                     newHeight);
441             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
442             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
443             ObjectAnimator oa =
444                     LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
445             ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f);
446             ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f);
447             ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f);
448             ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f);
449             oa.addUpdateListener(new AnimatorUpdateListener() {
450                 public void onAnimationUpdate(ValueAnimator animation) {
451                     requestLayout();
452                 }
453             });
454             AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
455             if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
456                 set.playTogether(oa, topOa, bottomOa);
457             } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
458                 set.playTogether(oa, leftOa, rightOa);
459             } else {
460                 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
461             }
462 
463             set.setDuration(SNAP_DURATION);
464             set.start();
465         }
466     }
467 }
468