blob: eca5ad7c4db16152c1982e413faa16a98f02ed8c [file] [log] [blame]
Adam Cohend4844c32011-02-18 19:25:06 -08001package 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;
Adam Cohen37b59ff2011-06-13 17:13:42 -070010import android.content.res.Resources;
Adam Cohend4844c32011-02-18 19:25:06 -080011import 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;
Adam Cohen67882692011-03-11 15:29:03 -080022 private DragLayer mDragLayer;
23 private Workspace mWorkspace;
Adam Cohend4844c32011-02-18 19:25:06 -080024 private ImageView mLeftHandle;
25 private ImageView mRightHandle;
Adam Cohen1b607ed2011-03-03 17:26:50 -080026 private ImageView mTopHandle;
Adam Cohend4844c32011-02-18 19:25:06 -080027 private ImageView mBottomHandle;
28
29 private boolean mLeftBorderActive;
30 private boolean mRightBorderActive;
31 private boolean mTopBorderActive;
32 private boolean mBottomBorderActive;
33
Adam Cohen37b59ff2011-06-13 17:13:42 -070034 private int mWidgetPaddingLeft;
35 private int mWidgetPaddingRight;
36 private int mWidgetPaddingTop;
37 private int mWidgetPaddingBottom;
38
Adam Cohend4844c32011-02-18 19:25:06 -080039 private int mBaselineWidth;
40 private int mBaselineHeight;
41 private int mBaselineX;
42 private int mBaselineY;
43 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080044
Adam Cohend4844c32011-02-18 19:25:06 -080045 private int mRunningHInc;
46 private int mRunningVInc;
47 private int mMinHSpan;
48 private int mMinVSpan;
49 private int mDeltaX;
50 private int mDeltaY;
Adam Cohen1b607ed2011-03-03 17:26:50 -080051
Adam Cohen3cba7222011-03-02 19:03:11 -080052 private int mBackgroundPadding;
53 private int mTouchTargetWidth;
Adam Cohend4844c32011-02-18 19:25:06 -080054
55 private int mExpandability[] = new int[4];
56
Adam Cohend4844c32011-02-18 19:25:06 -080057 final int SNAP_DURATION = 150;
Adam Cohen3cba7222011-03-02 19:03:11 -080058 final int BACKGROUND_PADDING = 24;
Adam Cohene4b77292011-03-08 18:35:52 -080059 final float DIMMED_HANDLE_ALPHA = 0f;
60 final float RESIZE_THRESHOLD = 0.66f;
Adam Cohend4844c32011-02-18 19:25:06 -080061
Adam Cohen1b607ed2011-03-03 17:26:50 -080062 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
Adam Cohend4844c32011-02-18 19:25:06 -080067 public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
Adam Cohen67882692011-03-11 15:29:03 -080068 LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -080069
70 super(context);
71 mContext = context;
72 mItemInfo = itemInfo;
73 mCellLayout = cellLayout;
74 mWidgetView = widgetView;
Adam Cohen27c09b02011-02-28 14:45:11 -080075 mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -080076 mDragLayer = dragLayer;
77 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
Adam Cohen3cba7222011-03-02 19:03:11 -080078
Adam Cohend4844c32011-02-18 19:25:06 -080079 final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
80 int[] result = mCellLayout.rectToCell(info.minWidth, info.minHeight, null);
81 mMinHSpan = result[0];
82 mMinVSpan = result[1];
83
Adam Cohen3cba7222011-03-02 19:03:11 -080084 setBackgroundResource(R.drawable.widget_resize_frame_holo);
Adam Cohend4844c32011-02-18 19:25:06 -080085 setPadding(0, 0, 0, 0);
86
87 LayoutParams lp;
88 mLeftHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080089 mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
Adam Cohend4844c32011-02-18 19:25:06 -080090 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
91 Gravity.LEFT | Gravity.CENTER_VERTICAL);
92 addView(mLeftHandle, lp);
93
94 mRightHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080095 mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
Adam Cohend4844c32011-02-18 19:25:06 -080096 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
97 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
98 addView(mRightHandle, lp);
99
100 mTopHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -0800101 mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
Adam Cohend4844c32011-02-18 19:25:06 -0800102 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
103 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
104 addView(mTopHandle, lp);
105
106 mBottomHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -0800107 mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
Adam Cohend4844c32011-02-18 19:25:06 -0800108 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
109 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
110 addView(mBottomHandle, lp);
111
Adam Cohen37b59ff2011-06-13 17:13:42 -0700112 Resources r = context.getResources();
113 mWidgetPaddingLeft = r.getDimensionPixelSize(R.dimen.app_widget_padding_left);
114 mWidgetPaddingTop = r.getDimensionPixelSize(R.dimen.app_widget_padding_top);
115 mWidgetPaddingRight = r.getDimensionPixelSize(R.dimen.app_widget_padding_right);
116 mWidgetPaddingBottom = r.getDimensionPixelSize(R.dimen.app_widget_padding_bottom);
117
Adam Cohend4844c32011-02-18 19:25:06 -0800118 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
119 mTopHandle.setVisibility(GONE);
120 mBottomHandle.setVisibility(GONE);
121 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
122 mLeftHandle.setVisibility(GONE);
123 mRightHandle.setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800124 }
125
126 final float density = mContext.getResources().getDisplayMetrics().density;
127 mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
128 mTouchTargetWidth = 2 * mBackgroundPadding;
Adam Cohend4844c32011-02-18 19:25:06 -0800129 }
130
131 public boolean beginResizeIfPointInRegion(int x, int y) {
132 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
133 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800134 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
135 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
136 mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
137 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800138
139 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
140 || mTopBorderActive || mBottomBorderActive;
141
142 mBaselineWidth = getMeasuredWidth();
143 mBaselineHeight = getMeasuredHeight();
144 mBaselineX = getLeft();
145 mBaselineY = getTop();
146 mRunningHInc = 0;
147 mRunningVInc = 0;
148
149 if (anyBordersActive) {
Adam Cohen3cba7222011-03-02 19:03:11 -0800150 mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
151 mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
152 mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
153 mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800154 }
155 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohen3cba7222011-03-02 19:03:11 -0800156
Adam Cohend4844c32011-02-18 19:25:06 -0800157 return anyBordersActive;
158 }
159
Adam Cohen1b607ed2011-03-03 17:26:50 -0800160 /**
161 * Here we bound the deltas such that the frame cannot be stretched beyond the extents
162 * of the CellLayout, and such that the frame's borders can't cross.
163 */
Adam Cohend4844c32011-02-18 19:25:06 -0800164 public void updateDeltas(int deltaX, int deltaY) {
165 if (mLeftBorderActive) {
166 mDeltaX = Math.max(-mBaselineX, deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800167 mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800168 } else if (mRightBorderActive) {
Adam Cohen67882692011-03-11 15:29:03 -0800169 mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800170 mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800171 }
172
173 if (mTopBorderActive) {
174 mDeltaY = Math.max(-mBaselineY, deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800175 mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800176 } else if (mBottomBorderActive) {
Adam Cohen67882692011-03-11 15:29:03 -0800177 mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800178 mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800179 }
180 }
181
Adam Cohen1b607ed2011-03-03 17:26:50 -0800182 /**
183 * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
184 */
Adam Cohend4844c32011-02-18 19:25:06 -0800185 public void visualizeResizeForDelta(int deltaX, int deltaY) {
186 updateDeltas(deltaX, deltaY);
Adam Cohen67882692011-03-11 15:29:03 -0800187 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
188
Adam Cohend4844c32011-02-18 19:25:06 -0800189 if (mLeftBorderActive) {
190 lp.x = mBaselineX + mDeltaX;
191 lp.width = mBaselineWidth - mDeltaX;
192 } else if (mRightBorderActive) {
193 lp.width = mBaselineWidth + mDeltaX;
194 }
195
196 if (mTopBorderActive) {
197 lp.y = mBaselineY + mDeltaY;
198 lp.height = mBaselineHeight - mDeltaY;
199 } else if (mBottomBorderActive) {
200 lp.height = mBaselineHeight + mDeltaY;
201 }
202
203 resizeWidgetIfNeeded();
204 requestLayout();
205 }
206
Adam Cohen1b607ed2011-03-03 17:26:50 -0800207 /**
208 * Based on the current deltas, we determine if and how to resize the widget.
209 */
Adam Cohend4844c32011-02-18 19:25:06 -0800210 private void resizeWidgetIfNeeded() {
Adam Cohend4844c32011-02-18 19:25:06 -0800211 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
212 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
213
Adam Cohene4b77292011-03-08 18:35:52 -0800214 float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc;
215 float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc;
216
217 int hSpanInc = 0;
218 int vSpanInc = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800219 int cellXInc = 0;
220 int cellYInc = 0;
221
Adam Cohene4b77292011-03-08 18:35:52 -0800222 if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
223 hSpanInc = Math.round(hSpanIncF);
224 }
225 if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
226 vSpanInc = Math.round(vSpanIncF);
227 }
228
Adam Cohend4844c32011-02-18 19:25:06 -0800229 if (hSpanInc == 0 && vSpanInc == 0) return;
230
231 // Before we change the widget, we clear the occupied cells associated with it.
232 // The new set of occupied cells is marked below, once the layout params are updated.
233 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
234
235 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800236
237 // For each border, we bound the resizing based on the minimum width, and the maximum
238 // expandability.
Adam Cohend4844c32011-02-18 19:25:06 -0800239 if (mLeftBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800240 cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800241 cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
242 hSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800243 hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800244 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
245 mRunningHInc -= hSpanInc;
246 } else if (mRightBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800247 hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800248 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
249 mRunningHInc += hSpanInc;
250 }
251
252 if (mTopBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800253 cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800254 cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
255 vSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800256 vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800257 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
258 mRunningVInc -= vSpanInc;
259 } else if (mBottomBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800260 vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800261 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
262 mRunningVInc += vSpanInc;
263 }
264
265 // Update the widget's dimensions and position according to the deltas computed above
266 if (mLeftBorderActive || mRightBorderActive) {
267 lp.cellHSpan += hSpanInc;
268 lp.cellX += cellXInc;
269 }
270
271 if (mTopBorderActive || mBottomBorderActive) {
272 lp.cellVSpan += vSpanInc;
273 lp.cellY += cellYInc;
274 }
275
Adam Cohen1b607ed2011-03-03 17:26:50 -0800276 // Update the expandability array, as we have changed the widget's size.
Adam Cohen3cba7222011-03-02 19:03:11 -0800277 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohend4844c32011-02-18 19:25:06 -0800278
279 // Update the cells occupied by this widget
280 mCellLayout.markCellsAsOccupiedForView(mWidgetView);
Adam Cohen67882692011-03-11 15:29:03 -0800281 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800282 }
283
Adam Cohen1b607ed2011-03-03 17:26:50 -0800284 /**
285 * This is the final step of the resize. Here we save the new widget size and position
286 * to LauncherModel and animate the resize frame.
287 */
Adam Cohend4844c32011-02-18 19:25:06 -0800288 public void commitResizeForDelta(int deltaX, int deltaY) {
289 visualizeResizeForDelta(deltaX, deltaY);
290
291 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
292 LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
293 lp.cellHSpan, lp.cellVSpan);
294 mWidgetView.requestLayout();
295
296 // Once our widget resizes (hence the post), we want to snap the resize frame to it
297 post(new Runnable() {
298 public void run() {
299 snapToWidget(true);
300 }
301 });
302 }
303
304 public void snapToWidget(boolean animate) {
Adam Cohen67882692011-03-11 15:29:03 -0800305 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Adam Cohen1c4b6ef2011-05-03 14:20:38 -0700306 int xOffset = mCellLayout.getLeft() + mCellLayout.getLeftPadding() - mWorkspace.getScrollX();
307 int yOffset = mCellLayout.getTop() + mCellLayout.getTopPadding() - mWorkspace.getScrollY();
Adam Cohend4844c32011-02-18 19:25:06 -0800308
Adam Cohen37b59ff2011-06-13 17:13:42 -0700309 int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
310 mWidgetPaddingRight;
311 int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
312 mWidgetPaddingBottom;
313
314 int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft;
315 int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop;
Adam Cohen3cba7222011-03-02 19:03:11 -0800316
317 // We need to make sure the frame stays within the bounds of the CellLayout
318 if (newY < 0) {
319 newHeight -= -newY;
320 newY = 0;
321 }
Adam Cohen67882692011-03-11 15:29:03 -0800322 if (newY + newHeight > mDragLayer.getHeight()) {
323 newHeight -= newY + newHeight - mDragLayer.getHeight();
Adam Cohen3cba7222011-03-02 19:03:11 -0800324 }
325
Adam Cohend4844c32011-02-18 19:25:06 -0800326 if (!animate) {
327 lp.width = newWidth;
328 lp.height = newHeight;
329 lp.x = newX;
330 lp.y = newY;
331 mLeftHandle.setAlpha(1.0f);
332 mRightHandle.setAlpha(1.0f);
333 mTopHandle.setAlpha(1.0f);
334 mBottomHandle.setAlpha(1.0f);
335 requestLayout();
336 } else {
337 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800338 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
339 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800340 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
341 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
342 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
343 ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
344 ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
345 ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
346 ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
347 oa.addUpdateListener(new AnimatorUpdateListener() {
348 public void onAnimationUpdate(ValueAnimator animation) {
349 requestLayout();
350 }
351 });
352 AnimatorSet set = new AnimatorSet();
353 if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
354 set.playTogether(oa, topOa, bottomOa);
355 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
356 set.playTogether(oa, leftOa, rightOa);
357 } else {
358 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
359 }
360
361 set.setDuration(SNAP_DURATION);
362 set.start();
363 }
364 }
365}