AppWidgetResizeFrame.java revision 200358df3f91d7f18b70319918bbab0680ed21ee
1package com.android.launcher2;
2
3import android.animation.AnimatorSet;
4import android.animation.ObjectAnimator;
5import android.animation.PropertyValuesHolder;
6import android.animation.ValueAnimator;
7import android.animation.ValueAnimator.AnimatorUpdateListener;
8import android.appwidget.AppWidgetHostView;
9import android.appwidget.AppWidgetProviderInfo;
10import android.content.Context;
11import android.graphics.Rect;
12import android.view.Gravity;
13import android.widget.FrameLayout;
14import android.widget.ImageView;
15
16import com.android.launcher.R;
17
18public class AppWidgetResizeFrame extends FrameLayout {
19
20    private ItemInfo mItemInfo;
21    private LauncherAppWidgetHostView mWidgetView;
22    private CellLayout mCellLayout;
23    private DragLayer mDragLayer;
24    private Workspace mWorkspace;
25    private ImageView mLeftHandle;
26    private ImageView mRightHandle;
27    private ImageView mTopHandle;
28    private ImageView mBottomHandle;
29
30    private boolean mLeftBorderActive;
31    private boolean mRightBorderActive;
32    private boolean mTopBorderActive;
33    private boolean mBottomBorderActive;
34
35    private int mWidgetPaddingLeft;
36    private int mWidgetPaddingRight;
37    private int mWidgetPaddingTop;
38    private int mWidgetPaddingBottom;
39
40    private int mBaselineWidth;
41    private int mBaselineHeight;
42    private int mBaselineX;
43    private int mBaselineY;
44    private int mResizeMode;
45
46    private int mRunningHInc;
47    private int mRunningVInc;
48    private int mMinHSpan;
49    private int mMinVSpan;
50    private int mDeltaX;
51    private int mDeltaY;
52    private int mDeltaXAddOn;
53    private int mDeltaYAddOn;
54
55    private int mBackgroundPadding;
56    private int mTouchTargetWidth;
57
58    int[] mDirectionVector = 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    public static final int LEFT = 0;
66    public static final int TOP = 1;
67    public static final int RIGHT = 2;
68    public static final int BOTTOM = 3;
69
70    private Launcher mLauncher;
71
72    public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
73            LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
74
75        super(context);
76        mLauncher = (Launcher) context;
77        mItemInfo = itemInfo;
78        mCellLayout = cellLayout;
79        mWidgetView = widgetView;
80        mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
81        mDragLayer = dragLayer;
82        mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
83
84        final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
85        int[] result = mLauncher.getMinSpanForWidget(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
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        mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
146        mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
147        mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
148        mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
149
150        boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
151                || mTopBorderActive || mBottomBorderActive;
152
153        mBaselineWidth = getMeasuredWidth();
154        mBaselineHeight = getMeasuredHeight();
155        mBaselineX = getLeft();
156        mBaselineY = getTop();
157
158        if (anyBordersActive) {
159            mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
160            mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
161            mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
162            mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
163        }
164        return anyBordersActive;
165    }
166
167    /**
168     *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
169     *  of the CellLayout, and such that the frame's borders can't cross.
170     */
171    public void updateDeltas(int deltaX, int deltaY) {
172        if (mLeftBorderActive) {
173            mDeltaX = Math.max(-mBaselineX, deltaX);
174            mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
175        } else if (mRightBorderActive) {
176            mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
177            mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
178        }
179
180        if (mTopBorderActive) {
181            mDeltaY = Math.max(-mBaselineY, deltaY);
182            mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
183        } else if (mBottomBorderActive) {
184            mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
185            mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
186        }
187    }
188
189    public void visualizeResizeForDelta(int deltaX, int deltaY) {
190        visualizeResizeForDelta(deltaX, deltaY, false);
191    }
192
193    /**
194     *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
195     */
196    private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
197        updateDeltas(deltaX, deltaY);
198        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
199
200        if (mLeftBorderActive) {
201            lp.x = mBaselineX + mDeltaX;
202            lp.width = mBaselineWidth - mDeltaX;
203        } else if (mRightBorderActive) {
204            lp.width = mBaselineWidth + mDeltaX;
205        }
206
207        if (mTopBorderActive) {
208            lp.y = mBaselineY + mDeltaY;
209            lp.height = mBaselineHeight - mDeltaY;
210        } else if (mBottomBorderActive) {
211            lp.height = mBaselineHeight + mDeltaY;
212        }
213
214        resizeWidgetIfNeeded(onDismiss);
215        requestLayout();
216    }
217
218    /**
219     *  Based on the current deltas, we determine if and how to resize the widget.
220     */
221    private void resizeWidgetIfNeeded(boolean onDismiss) {
222        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
223        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
224
225        int deltaX = mDeltaX + mDeltaXAddOn;
226        int deltaY = mDeltaY + mDeltaYAddOn;
227
228        float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
229        float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
230
231        int hSpanInc = 0;
232        int vSpanInc = 0;
233        int cellXInc = 0;
234        int cellYInc = 0;
235
236        int countX = mCellLayout.getCountX();
237        int countY = mCellLayout.getCountY();
238
239        if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
240            hSpanInc = Math.round(hSpanIncF);
241        }
242        if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
243            vSpanInc = Math.round(vSpanIncF);
244        }
245
246        if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
247
248
249        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
250
251        int spanX = lp.cellHSpan;
252        int spanY = lp.cellVSpan;
253        int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
254        int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
255
256        int hSpanDelta = 0;
257        int vSpanDelta = 0;
258
259        // For each border, we bound the resizing based on the minimum width, and the maximum
260        // expandability.
261        if (mLeftBorderActive) {
262            cellXInc = Math.max(-cellX, hSpanInc);
263            cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
264            hSpanInc *= -1;
265            hSpanInc = Math.min(cellX, hSpanInc);
266            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
267            hSpanDelta = -hSpanInc;
268
269        } else if (mRightBorderActive) {
270            hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
271            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
272            hSpanDelta = hSpanInc;
273        }
274
275        if (mTopBorderActive) {
276            cellYInc = Math.max(-cellY, vSpanInc);
277            cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
278            vSpanInc *= -1;
279            vSpanInc = Math.min(cellY, vSpanInc);
280            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
281            vSpanDelta = -vSpanInc;
282        } else if (mBottomBorderActive) {
283            vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
284            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
285            vSpanDelta = vSpanInc;
286        }
287
288        mDirectionVector[0] = 0;
289        mDirectionVector[1] = 0;
290        // Update the widget's dimensions and position according to the deltas computed above
291        if (mLeftBorderActive || mRightBorderActive) {
292            spanX += hSpanInc;
293            cellX += cellXInc;
294            mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
295        }
296
297        if (mTopBorderActive || mBottomBorderActive) {
298            spanY += vSpanInc;
299            cellY += cellYInc;
300            mDirectionVector[1] = mTopBorderActive ? -1 : 1;
301        }
302
303        if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
304
305        if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
306                mDirectionVector, onDismiss)) {
307            lp.tmpCellX = cellX;
308            lp.tmpCellY = cellY;
309            lp.cellHSpan = spanX;
310            lp.cellVSpan = spanY;
311            mRunningVInc += vSpanDelta;
312            mRunningHInc += hSpanDelta;
313        }
314
315        mWidgetView.requestLayout();
316    }
317
318    /**
319     * This is the final step of the resize. Here we save the new widget size and position
320     * to LauncherModel and animate the resize frame.
321     */
322    public void commitResize() {
323        resizeWidgetIfNeeded(true);
324        requestLayout();
325    }
326
327    public void onTouchUp() {
328        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
329        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
330
331        mDeltaXAddOn = mRunningHInc * xThreshold;
332        mDeltaYAddOn = mRunningVInc * yThreshold;
333        mDeltaX = 0;
334        mDeltaY = 0;
335
336        post(new Runnable() {
337            @Override
338            public void run() {
339                snapToWidget(true);
340            }
341        });
342    }
343
344    public void snapToWidget(boolean animate) {
345        final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
346        int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX();
347        int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY();
348
349        int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
350                mWidgetPaddingRight;
351        int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
352                mWidgetPaddingBottom;
353
354        int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft;
355        int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop;
356
357        // We need to make sure the frame stays within the bounds of the CellLayout
358        if (newY < 0) {
359            newHeight -= -newY;
360            newY = 0;
361        }
362        if (newY + newHeight > mDragLayer.getHeight()) {
363            newHeight -= newY + newHeight - mDragLayer.getHeight();
364        }
365
366        if (!animate) {
367            lp.width = newWidth;
368            lp.height = newHeight;
369            lp.x = newX;
370            lp.y = newY;
371            mLeftHandle.setAlpha(1.0f);
372            mRightHandle.setAlpha(1.0f);
373            mTopHandle.setAlpha(1.0f);
374            mBottomHandle.setAlpha(1.0f);
375            requestLayout();
376        } else {
377            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
378            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
379                    newHeight);
380            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
381            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
382            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
383            ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
384            ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
385            ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
386            ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
387            oa.addUpdateListener(new AnimatorUpdateListener() {
388                public void onAnimationUpdate(ValueAnimator animation) {
389                    requestLayout();
390                }
391            });
392            AnimatorSet set = new AnimatorSet();
393            if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
394                set.playTogether(oa, topOa, bottomOa);
395            } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
396                set.playTogether(oa, leftOa, rightOa);
397            } else {
398                set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
399            }
400
401            set.setDuration(SNAP_DURATION);
402            set.start();
403        }
404    }
405}
406