• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher2;
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.AppWidgetProviderInfo;
9 import android.content.Context;
10 import android.content.res.Resources;
11 import android.view.Gravity;
12 import android.widget.FrameLayout;
13 import android.widget.ImageView;
14 
15 import com.android.launcher.R;
16 
17 public class AppWidgetResizeFrame extends FrameLayout {
18 
19     private ItemInfo mItemInfo;
20     private LauncherAppWidgetHostView mWidgetView;
21     private CellLayout mCellLayout;
22     private DragLayer mDragLayer;
23     private Workspace mWorkspace;
24     private ImageView mLeftHandle;
25     private ImageView mRightHandle;
26     private ImageView mTopHandle;
27     private ImageView mBottomHandle;
28 
29     private boolean mLeftBorderActive;
30     private boolean mRightBorderActive;
31     private boolean mTopBorderActive;
32     private boolean mBottomBorderActive;
33 
34     private int mWidgetPaddingLeft;
35     private int mWidgetPaddingRight;
36     private int mWidgetPaddingTop;
37     private int mWidgetPaddingBottom;
38 
39     private int mBaselineWidth;
40     private int mBaselineHeight;
41     private int mBaselineX;
42     private int mBaselineY;
43     private int mResizeMode;
44 
45     private int mRunningHInc;
46     private int mRunningVInc;
47     private int mMinHSpan;
48     private int mMinVSpan;
49     private int mDeltaX;
50     private int mDeltaY;
51 
52     private int mBackgroundPadding;
53     private int mTouchTargetWidth;
54 
55     private int mExpandability[] = new int[4];
56 
57     final int SNAP_DURATION = 150;
58     final int BACKGROUND_PADDING = 24;
59     final float DIMMED_HANDLE_ALPHA = 0f;
60     final float RESIZE_THRESHOLD = 0.66f;
61 
62     public static final int LEFT = 0;
63     public static final int TOP = 1;
64     public static final int RIGHT = 2;
65     public static final int BOTTOM = 3;
66 
67     private Launcher mLauncher;
68 
AppWidgetResizeFrame(Context context, ItemInfo itemInfo, LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer)69     public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
70             LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
71 
72         super(context);
73         mLauncher = (Launcher) context;
74         mItemInfo = itemInfo;
75         mCellLayout = cellLayout;
76         mWidgetView = widgetView;
77         mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
78         mDragLayer = dragLayer;
79         mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
80 
81         final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
82         int[] result = mLauncher.getMinResizeSpanForWidget(info, null);
83         mMinHSpan = result[0];
84         mMinVSpan = result[1];
85 
86         setBackgroundResource(R.drawable.widget_resize_frame_holo);
87         setPadding(0, 0, 0, 0);
88 
89         LayoutParams lp;
90         mLeftHandle = new ImageView(context);
91         mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
92         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
93                 Gravity.LEFT | Gravity.CENTER_VERTICAL);
94         addView(mLeftHandle, lp);
95 
96         mRightHandle = new ImageView(context);
97         mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
98         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
99                 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
100         addView(mRightHandle, lp);
101 
102         mTopHandle = new ImageView(context);
103         mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
104         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
105                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
106         addView(mTopHandle, lp);
107 
108         mBottomHandle = new ImageView(context);
109         mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
110         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
111                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
112         addView(mBottomHandle, lp);
113 
114         Launcher.Padding p = mLauncher.getPaddingForWidget(widgetView.getAppWidgetInfo().provider);
115         mWidgetPaddingLeft = p.left;
116         mWidgetPaddingTop = p.top;
117         mWidgetPaddingRight = p.right;
118         mWidgetPaddingBottom = p.bottom;
119 
120         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
121             mTopHandle.setVisibility(GONE);
122             mBottomHandle.setVisibility(GONE);
123         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
124             mLeftHandle.setVisibility(GONE);
125             mRightHandle.setVisibility(GONE);
126         }
127 
128         final float density = mLauncher.getResources().getDisplayMetrics().density;
129         mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
130         mTouchTargetWidth = 2 * mBackgroundPadding;
131     }
132 
beginResizeIfPointInRegion(int x, int y)133     public boolean beginResizeIfPointInRegion(int x, int y) {
134         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
135         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
136         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
137         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
138         mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
139         mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
140 
141         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
142                 || mTopBorderActive || mBottomBorderActive;
143 
144         mBaselineWidth = getMeasuredWidth();
145         mBaselineHeight = getMeasuredHeight();
146         mBaselineX = getLeft();
147         mBaselineY = getTop();
148         mRunningHInc = 0;
149         mRunningVInc = 0;
150 
151         if (anyBordersActive) {
152             mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
153             mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
154             mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
155             mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
156         }
157         mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
158 
159         return anyBordersActive;
160     }
161 
162     /**
163      *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
164      *  of the CellLayout, and such that the frame's borders can't cross.
165      */
updateDeltas(int deltaX, int deltaY)166     public void updateDeltas(int deltaX, int deltaY) {
167         if (mLeftBorderActive) {
168             mDeltaX = Math.max(-mBaselineX, deltaX);
169             mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
170         } else if (mRightBorderActive) {
171             mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
172             mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
173         }
174 
175         if (mTopBorderActive) {
176             mDeltaY = Math.max(-mBaselineY, deltaY);
177             mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
178         } else if (mBottomBorderActive) {
179             mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
180             mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
181         }
182     }
183 
184     /**
185      *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
186      */
visualizeResizeForDelta(int deltaX, int deltaY)187     public void visualizeResizeForDelta(int deltaX, int deltaY) {
188         updateDeltas(deltaX, deltaY);
189         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
190 
191         if (mLeftBorderActive) {
192             lp.x = mBaselineX + mDeltaX;
193             lp.width = mBaselineWidth - mDeltaX;
194         } else if (mRightBorderActive) {
195             lp.width = mBaselineWidth + mDeltaX;
196         }
197 
198         if (mTopBorderActive) {
199             lp.y = mBaselineY + mDeltaY;
200             lp.height = mBaselineHeight - mDeltaY;
201         } else if (mBottomBorderActive) {
202             lp.height = mBaselineHeight + mDeltaY;
203         }
204 
205         resizeWidgetIfNeeded();
206         requestLayout();
207     }
208 
209     /**
210      *  Based on the current deltas, we determine if and how to resize the widget.
211      */
resizeWidgetIfNeeded()212     private void resizeWidgetIfNeeded() {
213         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
214         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
215 
216         float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc;
217         float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc;
218 
219         int hSpanInc = 0;
220         int vSpanInc = 0;
221         int cellXInc = 0;
222         int cellYInc = 0;
223 
224         if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
225             hSpanInc = Math.round(hSpanIncF);
226         }
227         if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
228             vSpanInc = Math.round(vSpanIncF);
229         }
230 
231         if (hSpanInc == 0 && vSpanInc == 0) return;
232 
233         // Before we change the widget, we clear the occupied cells associated with it.
234         // The new set of occupied cells is marked below, once the layout params are updated.
235         mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
236 
237         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
238 
239         // For each border, we bound the resizing based on the minimum width, and the maximum
240         // expandability.
241         if (mLeftBorderActive) {
242             cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
243             cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
244             hSpanInc *= -1;
245             hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
246             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
247             mRunningHInc -= hSpanInc;
248         } else if (mRightBorderActive) {
249             hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
250             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
251             mRunningHInc += hSpanInc;
252         }
253 
254         if (mTopBorderActive) {
255             cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
256             cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
257             vSpanInc *= -1;
258             vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
259             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
260             mRunningVInc -= vSpanInc;
261         } else if (mBottomBorderActive) {
262             vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
263             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
264             mRunningVInc += vSpanInc;
265         }
266 
267         // Update the widget's dimensions and position according to the deltas computed above
268         if (mLeftBorderActive || mRightBorderActive) {
269             lp.cellHSpan += hSpanInc;
270             lp.cellX += cellXInc;
271         }
272 
273         if (mTopBorderActive || mBottomBorderActive) {
274             lp.cellVSpan += vSpanInc;
275             lp.cellY += cellYInc;
276         }
277 
278         // Update the expandability array, as we have changed the widget's size.
279         mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
280 
281         // Update the cells occupied by this widget
282         mCellLayout.markCellsAsOccupiedForView(mWidgetView);
283         mWidgetView.requestLayout();
284     }
285 
286     /**
287      * This is the final step of the resize. Here we save the new widget size and position
288      * to LauncherModel and animate the resize frame.
289      */
commitResizeForDelta(int deltaX, int deltaY)290     public void commitResizeForDelta(int deltaX, int deltaY) {
291         visualizeResizeForDelta(deltaX, deltaY);
292 
293         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
294         LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
295                 lp.cellHSpan, lp.cellVSpan);
296         mWidgetView.requestLayout();
297 
298         // Once our widget resizes (hence the post), we want to snap the resize frame to it
299         post(new Runnable() {
300             public void run() {
301                 snapToWidget(true);
302             }
303         });
304     }
305 
snapToWidget(boolean animate)306     public void snapToWidget(boolean animate) {
307         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
308         int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX();
309         int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY();
310 
311         int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
312                 mWidgetPaddingRight;
313         int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
314                 mWidgetPaddingBottom;
315 
316         int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft;
317         int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop;
318 
319         // We need to make sure the frame stays within the bounds of the CellLayout
320         if (newY < 0) {
321             newHeight -= -newY;
322             newY = 0;
323         }
324         if (newY + newHeight > mDragLayer.getHeight()) {
325             newHeight -= newY + newHeight - mDragLayer.getHeight();
326         }
327 
328         if (!animate) {
329             lp.width = newWidth;
330             lp.height = newHeight;
331             lp.x = newX;
332             lp.y = newY;
333             mLeftHandle.setAlpha(1.0f);
334             mRightHandle.setAlpha(1.0f);
335             mTopHandle.setAlpha(1.0f);
336             mBottomHandle.setAlpha(1.0f);
337             requestLayout();
338         } else {
339             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
340             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
341                     newHeight);
342             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
343             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
344             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
345             ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
346             ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
347             ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
348             ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
349             oa.addUpdateListener(new AnimatorUpdateListener() {
350                 public void onAnimationUpdate(ValueAnimator animation) {
351                     requestLayout();
352                 }
353             });
354             AnimatorSet set = new AnimatorSet();
355             if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
356                 set.playTogether(oa, topOa, bottomOa);
357             } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
358                 set.playTogether(oa, leftOa, rightOa);
359             } else {
360                 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
361             }
362 
363             set.setDuration(SNAP_DURATION);
364             set.start();
365         }
366     }
367 }
368