blob: d975ed93114a9eec6076749e44d2c4e3ba4b0d6a [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;
10import android.view.Gravity;
11import android.widget.FrameLayout;
12import android.widget.ImageView;
13
14import com.android.launcher.R;
15
16public class AppWidgetResizeFrame extends FrameLayout {
17
18 private ItemInfo mItemInfo;
19 private LauncherAppWidgetHostView mWidgetView;
20 private CellLayout mCellLayout;
Adam Cohen67882692011-03-11 15:29:03 -080021 private DragLayer mDragLayer;
22 private Workspace mWorkspace;
Adam Cohend4844c32011-02-18 19:25:06 -080023 private ImageView mLeftHandle;
24 private ImageView mRightHandle;
Adam Cohen1b607ed2011-03-03 17:26:50 -080025 private ImageView mTopHandle;
Adam Cohend4844c32011-02-18 19:25:06 -080026 private ImageView mBottomHandle;
27
28 private boolean mLeftBorderActive;
29 private boolean mRightBorderActive;
30 private boolean mTopBorderActive;
31 private boolean mBottomBorderActive;
32
33 private int mBaselineWidth;
34 private int mBaselineHeight;
35 private int mBaselineX;
36 private int mBaselineY;
37 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080038
Adam Cohend4844c32011-02-18 19:25:06 -080039 private int mRunningHInc;
40 private int mRunningVInc;
41 private int mMinHSpan;
42 private int mMinVSpan;
43 private int mDeltaX;
44 private int mDeltaY;
Adam Cohen1b607ed2011-03-03 17:26:50 -080045
Adam Cohen3cba7222011-03-02 19:03:11 -080046 private int mBackgroundPadding;
47 private int mTouchTargetWidth;
Adam Cohend4844c32011-02-18 19:25:06 -080048
49 private int mExpandability[] = new int[4];
50
Adam Cohend4844c32011-02-18 19:25:06 -080051 final int SNAP_DURATION = 150;
Adam Cohen3cba7222011-03-02 19:03:11 -080052 final int BACKGROUND_PADDING = 24;
Adam Cohene4b77292011-03-08 18:35:52 -080053 final float DIMMED_HANDLE_ALPHA = 0f;
54 final float RESIZE_THRESHOLD = 0.66f;
Adam Cohend4844c32011-02-18 19:25:06 -080055
Adam Cohen1b607ed2011-03-03 17:26:50 -080056 public static final int LEFT = 0;
57 public static final int TOP = 1;
58 public static final int RIGHT = 2;
59 public static final int BOTTOM = 3;
60
Adam Cohend4844c32011-02-18 19:25:06 -080061 public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
Adam Cohen67882692011-03-11 15:29:03 -080062 LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -080063
64 super(context);
65 mContext = context;
66 mItemInfo = itemInfo;
67 mCellLayout = cellLayout;
68 mWidgetView = widgetView;
Adam Cohen27c09b02011-02-28 14:45:11 -080069 mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -080070 mDragLayer = dragLayer;
71 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
Adam Cohen3cba7222011-03-02 19:03:11 -080072
Adam Cohend4844c32011-02-18 19:25:06 -080073 final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
74 int[] result = mCellLayout.rectToCell(info.minWidth, info.minHeight, null);
75 mMinHSpan = result[0];
76 mMinVSpan = result[1];
77
Adam Cohen3cba7222011-03-02 19:03:11 -080078 setBackgroundResource(R.drawable.widget_resize_frame_holo);
Adam Cohend4844c32011-02-18 19:25:06 -080079 setPadding(0, 0, 0, 0);
80
81 LayoutParams lp;
82 mLeftHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080083 mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
Adam Cohend4844c32011-02-18 19:25:06 -080084 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
85 Gravity.LEFT | Gravity.CENTER_VERTICAL);
86 addView(mLeftHandle, lp);
87
88 mRightHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080089 mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
Adam Cohend4844c32011-02-18 19:25:06 -080090 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
91 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
92 addView(mRightHandle, lp);
93
94 mTopHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080095 mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
Adam Cohend4844c32011-02-18 19:25:06 -080096 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
97 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
98 addView(mTopHandle, lp);
99
100 mBottomHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -0800101 mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
Adam Cohend4844c32011-02-18 19:25:06 -0800102 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
103 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
104 addView(mBottomHandle, lp);
105
106 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
107 mTopHandle.setVisibility(GONE);
108 mBottomHandle.setVisibility(GONE);
109 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
110 mLeftHandle.setVisibility(GONE);
111 mRightHandle.setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800112 }
113
114 final float density = mContext.getResources().getDisplayMetrics().density;
115 mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
116 mTouchTargetWidth = 2 * mBackgroundPadding;
Adam Cohend4844c32011-02-18 19:25:06 -0800117 }
118
119 public boolean beginResizeIfPointInRegion(int x, int y) {
120 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
121 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800122 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
123 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
124 mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
125 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800126
127 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
128 || mTopBorderActive || mBottomBorderActive;
129
130 mBaselineWidth = getMeasuredWidth();
131 mBaselineHeight = getMeasuredHeight();
132 mBaselineX = getLeft();
133 mBaselineY = getTop();
134 mRunningHInc = 0;
135 mRunningVInc = 0;
136
137 if (anyBordersActive) {
Adam Cohen3cba7222011-03-02 19:03:11 -0800138 mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
139 mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
140 mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
141 mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800142 }
143 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohen3cba7222011-03-02 19:03:11 -0800144
Adam Cohend4844c32011-02-18 19:25:06 -0800145 return anyBordersActive;
146 }
147
Adam Cohen1b607ed2011-03-03 17:26:50 -0800148 /**
149 * Here we bound the deltas such that the frame cannot be stretched beyond the extents
150 * of the CellLayout, and such that the frame's borders can't cross.
151 */
Adam Cohend4844c32011-02-18 19:25:06 -0800152 public void updateDeltas(int deltaX, int deltaY) {
153 if (mLeftBorderActive) {
154 mDeltaX = Math.max(-mBaselineX, deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800155 mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800156 } else if (mRightBorderActive) {
Adam Cohen67882692011-03-11 15:29:03 -0800157 mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800158 mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800159 }
160
161 if (mTopBorderActive) {
162 mDeltaY = Math.max(-mBaselineY, deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800163 mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800164 } else if (mBottomBorderActive) {
Adam Cohen67882692011-03-11 15:29:03 -0800165 mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800166 mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800167 }
168 }
169
Adam Cohen1b607ed2011-03-03 17:26:50 -0800170 /**
171 * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
172 */
Adam Cohend4844c32011-02-18 19:25:06 -0800173 public void visualizeResizeForDelta(int deltaX, int deltaY) {
174 updateDeltas(deltaX, deltaY);
Adam Cohen67882692011-03-11 15:29:03 -0800175 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
176
Adam Cohend4844c32011-02-18 19:25:06 -0800177 if (mLeftBorderActive) {
178 lp.x = mBaselineX + mDeltaX;
179 lp.width = mBaselineWidth - mDeltaX;
180 } else if (mRightBorderActive) {
181 lp.width = mBaselineWidth + mDeltaX;
182 }
183
184 if (mTopBorderActive) {
185 lp.y = mBaselineY + mDeltaY;
186 lp.height = mBaselineHeight - mDeltaY;
187 } else if (mBottomBorderActive) {
188 lp.height = mBaselineHeight + mDeltaY;
189 }
190
191 resizeWidgetIfNeeded();
192 requestLayout();
193 }
194
Adam Cohen1b607ed2011-03-03 17:26:50 -0800195 /**
196 * Based on the current deltas, we determine if and how to resize the widget.
197 */
Adam Cohend4844c32011-02-18 19:25:06 -0800198 private void resizeWidgetIfNeeded() {
Adam Cohend4844c32011-02-18 19:25:06 -0800199 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
200 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
201
Adam Cohene4b77292011-03-08 18:35:52 -0800202 float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc;
203 float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc;
204
205 int hSpanInc = 0;
206 int vSpanInc = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800207 int cellXInc = 0;
208 int cellYInc = 0;
209
Adam Cohene4b77292011-03-08 18:35:52 -0800210 if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
211 hSpanInc = Math.round(hSpanIncF);
212 }
213 if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
214 vSpanInc = Math.round(vSpanIncF);
215 }
216
Adam Cohend4844c32011-02-18 19:25:06 -0800217 if (hSpanInc == 0 && vSpanInc == 0) return;
218
219 // Before we change the widget, we clear the occupied cells associated with it.
220 // The new set of occupied cells is marked below, once the layout params are updated.
221 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
222
223 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800224
225 // For each border, we bound the resizing based on the minimum width, and the maximum
226 // expandability.
Adam Cohend4844c32011-02-18 19:25:06 -0800227 if (mLeftBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800228 cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800229 cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
230 hSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800231 hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800232 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
233 mRunningHInc -= hSpanInc;
234 } else if (mRightBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800235 hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800236 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
237 mRunningHInc += hSpanInc;
238 }
239
240 if (mTopBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800241 cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800242 cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
243 vSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800244 vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800245 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
246 mRunningVInc -= vSpanInc;
247 } else if (mBottomBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800248 vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800249 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
250 mRunningVInc += vSpanInc;
251 }
252
253 // Update the widget's dimensions and position according to the deltas computed above
254 if (mLeftBorderActive || mRightBorderActive) {
255 lp.cellHSpan += hSpanInc;
256 lp.cellX += cellXInc;
257 }
258
259 if (mTopBorderActive || mBottomBorderActive) {
260 lp.cellVSpan += vSpanInc;
261 lp.cellY += cellYInc;
262 }
263
Adam Cohen1b607ed2011-03-03 17:26:50 -0800264 // Update the expandability array, as we have changed the widget's size.
Adam Cohen3cba7222011-03-02 19:03:11 -0800265 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohend4844c32011-02-18 19:25:06 -0800266
267 // Update the cells occupied by this widget
268 mCellLayout.markCellsAsOccupiedForView(mWidgetView);
Adam Cohen67882692011-03-11 15:29:03 -0800269 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800270 }
271
Adam Cohen1b607ed2011-03-03 17:26:50 -0800272 /**
273 * This is the final step of the resize. Here we save the new widget size and position
274 * to LauncherModel and animate the resize frame.
275 */
Adam Cohend4844c32011-02-18 19:25:06 -0800276 public void commitResizeForDelta(int deltaX, int deltaY) {
277 visualizeResizeForDelta(deltaX, deltaY);
278
279 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
280 LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
281 lp.cellHSpan, lp.cellVSpan);
282 mWidgetView.requestLayout();
283
284 // Once our widget resizes (hence the post), we want to snap the resize frame to it
285 post(new Runnable() {
286 public void run() {
287 snapToWidget(true);
288 }
289 });
290 }
291
292 public void snapToWidget(boolean animate) {
Adam Cohen67882692011-03-11 15:29:03 -0800293 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
294 int xOffset = mCellLayout.getLeft() - mWorkspace.getScrollX();
295 int yOffset = mCellLayout.getTop() - mWorkspace.getScrollY();
Adam Cohend4844c32011-02-18 19:25:06 -0800296
Adam Cohen3cba7222011-03-02 19:03:11 -0800297 int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding;
298 int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding;
Adam Cohen67882692011-03-11 15:29:03 -0800299 int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset;
300 int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset;
Adam Cohen3cba7222011-03-02 19:03:11 -0800301
302 // We need to make sure the frame stays within the bounds of the CellLayout
303 if (newY < 0) {
304 newHeight -= -newY;
305 newY = 0;
306 }
Adam Cohen67882692011-03-11 15:29:03 -0800307 if (newY + newHeight > mDragLayer.getHeight()) {
308 newHeight -= newY + newHeight - mDragLayer.getHeight();
Adam Cohen3cba7222011-03-02 19:03:11 -0800309 }
310
Adam Cohend4844c32011-02-18 19:25:06 -0800311 if (!animate) {
312 lp.width = newWidth;
313 lp.height = newHeight;
314 lp.x = newX;
315 lp.y = newY;
316 mLeftHandle.setAlpha(1.0f);
317 mRightHandle.setAlpha(1.0f);
318 mTopHandle.setAlpha(1.0f);
319 mBottomHandle.setAlpha(1.0f);
320 requestLayout();
321 } else {
322 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800323 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
324 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800325 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
326 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
327 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
328 ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
329 ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
330 ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
331 ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
332 oa.addUpdateListener(new AnimatorUpdateListener() {
333 public void onAnimationUpdate(ValueAnimator animation) {
334 requestLayout();
335 }
336 });
337 AnimatorSet set = new AnimatorSet();
338 if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
339 set.playTogether(oa, topOa, bottomOa);
340 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
341 set.playTogether(oa, leftOa, rightOa);
342 } else {
343 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
344 }
345
346 set.setDuration(SNAP_DURATION);
347 set.start();
348 }
349 }
350}