blob: 2b2662f423b6b996d56c0bfdbaebef32ff514fff [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;
21 private ImageView mLeftHandle;
22 private ImageView mRightHandle;
Adam Cohen1b607ed2011-03-03 17:26:50 -080023 private ImageView mTopHandle;
Adam Cohend4844c32011-02-18 19:25:06 -080024 private ImageView mBottomHandle;
25
26 private boolean mLeftBorderActive;
27 private boolean mRightBorderActive;
28 private boolean mTopBorderActive;
29 private boolean mBottomBorderActive;
30
31 private int mBaselineWidth;
32 private int mBaselineHeight;
33 private int mBaselineX;
34 private int mBaselineY;
35 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080036
Adam Cohend4844c32011-02-18 19:25:06 -080037 private int mRunningHInc;
38 private int mRunningVInc;
39 private int mMinHSpan;
40 private int mMinVSpan;
41 private int mDeltaX;
42 private int mDeltaY;
Adam Cohen1b607ed2011-03-03 17:26:50 -080043
Adam Cohen3cba7222011-03-02 19:03:11 -080044 private int mBackgroundPadding;
45 private int mTouchTargetWidth;
Adam Cohend4844c32011-02-18 19:25:06 -080046
47 private int mExpandability[] = new int[4];
48
Adam Cohend4844c32011-02-18 19:25:06 -080049 final int SNAP_DURATION = 150;
Adam Cohen3cba7222011-03-02 19:03:11 -080050 final int BACKGROUND_PADDING = 24;
Adam Cohene4b77292011-03-08 18:35:52 -080051 final float DIMMED_HANDLE_ALPHA = 0f;
52 final float RESIZE_THRESHOLD = 0.66f;
Adam Cohend4844c32011-02-18 19:25:06 -080053
Adam Cohen1b607ed2011-03-03 17:26:50 -080054 public static final int LEFT = 0;
55 public static final int TOP = 1;
56 public static final int RIGHT = 2;
57 public static final int BOTTOM = 3;
58
Adam Cohend4844c32011-02-18 19:25:06 -080059 public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
60 LauncherAppWidgetHostView widgetView, CellLayout cellLayout) {
61
62 super(context);
63 mContext = context;
64 mItemInfo = itemInfo;
65 mCellLayout = cellLayout;
66 mWidgetView = widgetView;
Adam Cohen27c09b02011-02-28 14:45:11 -080067 mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080068
Adam Cohend4844c32011-02-18 19:25:06 -080069 final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
70 int[] result = mCellLayout.rectToCell(info.minWidth, info.minHeight, null);
71 mMinHSpan = result[0];
72 mMinVSpan = result[1];
73
Adam Cohen3cba7222011-03-02 19:03:11 -080074 setBackgroundResource(R.drawable.widget_resize_frame_holo);
Adam Cohend4844c32011-02-18 19:25:06 -080075 setPadding(0, 0, 0, 0);
76
77 LayoutParams lp;
78 mLeftHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080079 mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
Adam Cohend4844c32011-02-18 19:25:06 -080080 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
81 Gravity.LEFT | Gravity.CENTER_VERTICAL);
82 addView(mLeftHandle, lp);
83
84 mRightHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080085 mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
Adam Cohend4844c32011-02-18 19:25:06 -080086 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
87 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
88 addView(mRightHandle, lp);
89
90 mTopHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080091 mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
Adam Cohend4844c32011-02-18 19:25:06 -080092 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
93 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
94 addView(mTopHandle, lp);
95
96 mBottomHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080097 mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
Adam Cohend4844c32011-02-18 19:25:06 -080098 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
99 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
100 addView(mBottomHandle, lp);
101
102 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
103 mTopHandle.setVisibility(GONE);
104 mBottomHandle.setVisibility(GONE);
105 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
106 mLeftHandle.setVisibility(GONE);
107 mRightHandle.setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800108 }
109
110 final float density = mContext.getResources().getDisplayMetrics().density;
111 mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
112 mTouchTargetWidth = 2 * mBackgroundPadding;
Adam Cohend4844c32011-02-18 19:25:06 -0800113 }
114
115 public boolean beginResizeIfPointInRegion(int x, int y) {
116 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
117 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800118 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
119 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
120 mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
121 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800122
123 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
124 || mTopBorderActive || mBottomBorderActive;
125
126 mBaselineWidth = getMeasuredWidth();
127 mBaselineHeight = getMeasuredHeight();
128 mBaselineX = getLeft();
129 mBaselineY = getTop();
130 mRunningHInc = 0;
131 mRunningVInc = 0;
132
133 if (anyBordersActive) {
Adam Cohen3cba7222011-03-02 19:03:11 -0800134 mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
135 mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
136 mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
137 mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800138 }
139 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohen3cba7222011-03-02 19:03:11 -0800140
Adam Cohend4844c32011-02-18 19:25:06 -0800141 return anyBordersActive;
142 }
143
Adam Cohen1b607ed2011-03-03 17:26:50 -0800144 /**
145 * Here we bound the deltas such that the frame cannot be stretched beyond the extents
146 * of the CellLayout, and such that the frame's borders can't cross.
147 */
Adam Cohend4844c32011-02-18 19:25:06 -0800148 public void updateDeltas(int deltaX, int deltaY) {
149 if (mLeftBorderActive) {
150 mDeltaX = Math.max(-mBaselineX, deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800151 mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800152 } else if (mRightBorderActive) {
153 mDeltaX = Math.min(mCellLayout.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800154 mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800155 }
156
157 if (mTopBorderActive) {
158 mDeltaY = Math.max(-mBaselineY, deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800159 mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800160 } else if (mBottomBorderActive) {
161 mDeltaY = Math.min(mCellLayout.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800162 mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800163 }
164 }
165
Adam Cohen1b607ed2011-03-03 17:26:50 -0800166 /**
167 * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
168 */
Adam Cohend4844c32011-02-18 19:25:06 -0800169 public void visualizeResizeForDelta(int deltaX, int deltaY) {
170 updateDeltas(deltaX, deltaY);
171 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
172 if (mLeftBorderActive) {
173 lp.x = mBaselineX + mDeltaX;
174 lp.width = mBaselineWidth - mDeltaX;
175 } else if (mRightBorderActive) {
176 lp.width = mBaselineWidth + mDeltaX;
177 }
178
179 if (mTopBorderActive) {
180 lp.y = mBaselineY + mDeltaY;
181 lp.height = mBaselineHeight - mDeltaY;
182 } else if (mBottomBorderActive) {
183 lp.height = mBaselineHeight + mDeltaY;
184 }
185
186 resizeWidgetIfNeeded();
187 requestLayout();
188 }
189
Adam Cohen1b607ed2011-03-03 17:26:50 -0800190 /**
191 * Based on the current deltas, we determine if and how to resize the widget.
192 */
Adam Cohend4844c32011-02-18 19:25:06 -0800193 private void resizeWidgetIfNeeded() {
Adam Cohend4844c32011-02-18 19:25:06 -0800194 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
195 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
196
Adam Cohene4b77292011-03-08 18:35:52 -0800197 float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc;
198 float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc;
199
200 int hSpanInc = 0;
201 int vSpanInc = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800202 int cellXInc = 0;
203 int cellYInc = 0;
204
Adam Cohene4b77292011-03-08 18:35:52 -0800205 if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
206 hSpanInc = Math.round(hSpanIncF);
207 }
208 if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
209 vSpanInc = Math.round(vSpanIncF);
210 }
211
Adam Cohend4844c32011-02-18 19:25:06 -0800212 if (hSpanInc == 0 && vSpanInc == 0) return;
213
214 // Before we change the widget, we clear the occupied cells associated with it.
215 // The new set of occupied cells is marked below, once the layout params are updated.
216 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
217
218 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800219
220 // For each border, we bound the resizing based on the minimum width, and the maximum
221 // expandability.
Adam Cohend4844c32011-02-18 19:25:06 -0800222 if (mLeftBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800223 cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800224 cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
225 hSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800226 hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800227 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
228 mRunningHInc -= hSpanInc;
229 } else if (mRightBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800230 hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800231 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
232 mRunningHInc += hSpanInc;
233 }
234
235 if (mTopBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800236 cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800237 cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
238 vSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800239 vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800240 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
241 mRunningVInc -= vSpanInc;
242 } else if (mBottomBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800243 vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800244 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
245 mRunningVInc += vSpanInc;
246 }
247
248 // Update the widget's dimensions and position according to the deltas computed above
249 if (mLeftBorderActive || mRightBorderActive) {
250 lp.cellHSpan += hSpanInc;
251 lp.cellX += cellXInc;
252 }
253
254 if (mTopBorderActive || mBottomBorderActive) {
255 lp.cellVSpan += vSpanInc;
256 lp.cellY += cellYInc;
257 }
258
Adam Cohen1b607ed2011-03-03 17:26:50 -0800259 // Update the expandability array, as we have changed the widget's size.
Adam Cohen3cba7222011-03-02 19:03:11 -0800260 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohend4844c32011-02-18 19:25:06 -0800261
262 // Update the cells occupied by this widget
263 mCellLayout.markCellsAsOccupiedForView(mWidgetView);
264 }
265
Adam Cohen1b607ed2011-03-03 17:26:50 -0800266 /**
267 * This is the final step of the resize. Here we save the new widget size and position
268 * to LauncherModel and animate the resize frame.
269 */
Adam Cohend4844c32011-02-18 19:25:06 -0800270 public void commitResizeForDelta(int deltaX, int deltaY) {
271 visualizeResizeForDelta(deltaX, deltaY);
272
273 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
274 LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
275 lp.cellHSpan, lp.cellVSpan);
276 mWidgetView.requestLayout();
277
278 // Once our widget resizes (hence the post), we want to snap the resize frame to it
279 post(new Runnable() {
280 public void run() {
281 snapToWidget(true);
282 }
283 });
284 }
285
286 public void snapToWidget(boolean animate) {
287 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
288
Adam Cohen3cba7222011-03-02 19:03:11 -0800289 int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding;
290 int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding;
291 int newX = mWidgetView.getLeft() - mBackgroundPadding;
292 int newY = mWidgetView.getTop() - mBackgroundPadding;
293
294 // We need to make sure the frame stays within the bounds of the CellLayout
295 if (newY < 0) {
296 newHeight -= -newY;
297 newY = 0;
298 }
299 if (newY + newHeight > mCellLayout.getHeight()) {
300 newHeight -= newY + newHeight - mCellLayout.getHeight();
301 }
302
Adam Cohend4844c32011-02-18 19:25:06 -0800303 if (!animate) {
304 lp.width = newWidth;
305 lp.height = newHeight;
306 lp.x = newX;
307 lp.y = newY;
308 mLeftHandle.setAlpha(1.0f);
309 mRightHandle.setAlpha(1.0f);
310 mTopHandle.setAlpha(1.0f);
311 mBottomHandle.setAlpha(1.0f);
312 requestLayout();
313 } else {
314 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800315 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
316 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800317 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
318 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
319 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
320 ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
321 ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
322 ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
323 ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
324 oa.addUpdateListener(new AnimatorUpdateListener() {
325 public void onAnimationUpdate(ValueAnimator animation) {
326 requestLayout();
327 }
328 });
329 AnimatorSet set = new AnimatorSet();
330 if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
331 set.playTogether(oa, topOa, bottomOa);
332 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
333 set.playTogether(oa, leftOa, rightOa);
334 } else {
335 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
336 }
337
338 set.setDuration(SNAP_DURATION);
339 set.start();
340 }
341 }
342}