AppWidgetResizeFrame.java revision cbf47e38e24cc0d63b4bd3f345c050216f06f404
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.AppWidgetProviderInfo;
9import android.content.Context;
10import android.content.res.Resources;
11import android.view.Gravity;
12import android.widget.FrameLayout;
13import android.widget.ImageView;
14
15import com.android.launcher.R;
16
17public 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
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        Resources r = context.getResources();
115        mWidgetPaddingLeft = r.getDimensionPixelSize(R.dimen.app_widget_padding_left);
116        mWidgetPaddingTop = r.getDimensionPixelSize(R.dimen.app_widget_padding_top);
117        mWidgetPaddingRight = r.getDimensionPixelSize(R.dimen.app_widget_padding_right);
118        mWidgetPaddingBottom = r.getDimensionPixelSize(R.dimen.app_widget_padding_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
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     */
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     */
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     */
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     */
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
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