blob: 5f725b7ba3998efa44afc127dc57e38ddecc6a5f [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;
51 final float DIMMED_HANDLE_ALPHA = 0.3f;
Adam Cohend4844c32011-02-18 19:25:06 -080052
Adam Cohen1b607ed2011-03-03 17:26:50 -080053 public static final int LEFT = 0;
54 public static final int TOP = 1;
55 public static final int RIGHT = 2;
56 public static final int BOTTOM = 3;
57
Adam Cohend4844c32011-02-18 19:25:06 -080058 public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
59 LauncherAppWidgetHostView widgetView, CellLayout cellLayout) {
60
61 super(context);
62 mContext = context;
63 mItemInfo = itemInfo;
64 mCellLayout = cellLayout;
65 mWidgetView = widgetView;
Adam Cohen27c09b02011-02-28 14:45:11 -080066 mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080067
Adam Cohend4844c32011-02-18 19:25:06 -080068 final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
69 int[] result = mCellLayout.rectToCell(info.minWidth, info.minHeight, null);
70 mMinHSpan = result[0];
71 mMinVSpan = result[1];
72
Adam Cohen3cba7222011-03-02 19:03:11 -080073 setBackgroundResource(R.drawable.widget_resize_frame_holo);
Adam Cohend4844c32011-02-18 19:25:06 -080074 setPadding(0, 0, 0, 0);
75
76 LayoutParams lp;
77 mLeftHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080078 mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
Adam Cohend4844c32011-02-18 19:25:06 -080079 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
80 Gravity.LEFT | Gravity.CENTER_VERTICAL);
81 addView(mLeftHandle, lp);
82
83 mRightHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080084 mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
Adam Cohend4844c32011-02-18 19:25:06 -080085 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
86 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
87 addView(mRightHandle, lp);
88
89 mTopHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080090 mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
Adam Cohend4844c32011-02-18 19:25:06 -080091 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
92 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
93 addView(mTopHandle, lp);
94
95 mBottomHandle = new ImageView(context);
Adam Cohen3cba7222011-03-02 19:03:11 -080096 mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
Adam Cohend4844c32011-02-18 19:25:06 -080097 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
98 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
99 addView(mBottomHandle, lp);
100
101 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
102 mTopHandle.setVisibility(GONE);
103 mBottomHandle.setVisibility(GONE);
104 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
105 mLeftHandle.setVisibility(GONE);
106 mRightHandle.setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800107 }
108
109 final float density = mContext.getResources().getDisplayMetrics().density;
110 mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
111 mTouchTargetWidth = 2 * mBackgroundPadding;
Adam Cohend4844c32011-02-18 19:25:06 -0800112 }
113
114 public boolean beginResizeIfPointInRegion(int x, int y) {
115 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
116 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800117 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
118 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
119 mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
120 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800121
122 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
123 || mTopBorderActive || mBottomBorderActive;
124
125 mBaselineWidth = getMeasuredWidth();
126 mBaselineHeight = getMeasuredHeight();
127 mBaselineX = getLeft();
128 mBaselineY = getTop();
129 mRunningHInc = 0;
130 mRunningVInc = 0;
131
132 if (anyBordersActive) {
Adam Cohen3cba7222011-03-02 19:03:11 -0800133 mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
134 mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
135 mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
136 mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800137 }
138 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohen3cba7222011-03-02 19:03:11 -0800139
Adam Cohend4844c32011-02-18 19:25:06 -0800140 return anyBordersActive;
141 }
142
Adam Cohen1b607ed2011-03-03 17:26:50 -0800143 /**
144 * Here we bound the deltas such that the frame cannot be stretched beyond the extents
145 * of the CellLayout, and such that the frame's borders can't cross.
146 */
Adam Cohend4844c32011-02-18 19:25:06 -0800147 public void updateDeltas(int deltaX, int deltaY) {
148 if (mLeftBorderActive) {
149 mDeltaX = Math.max(-mBaselineX, deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800150 mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800151 } else if (mRightBorderActive) {
152 mDeltaX = Math.min(mCellLayout.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
Adam Cohen3cba7222011-03-02 19:03:11 -0800153 mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
Adam Cohend4844c32011-02-18 19:25:06 -0800154 }
155
156 if (mTopBorderActive) {
157 mDeltaY = Math.max(-mBaselineY, deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800158 mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800159 } else if (mBottomBorderActive) {
160 mDeltaY = Math.min(mCellLayout.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
Adam Cohen3cba7222011-03-02 19:03:11 -0800161 mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800162 }
163 }
164
Adam Cohen1b607ed2011-03-03 17:26:50 -0800165 /**
166 * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
167 */
Adam Cohend4844c32011-02-18 19:25:06 -0800168 public void visualizeResizeForDelta(int deltaX, int deltaY) {
169 updateDeltas(deltaX, deltaY);
170 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
171 if (mLeftBorderActive) {
172 lp.x = mBaselineX + mDeltaX;
173 lp.width = mBaselineWidth - mDeltaX;
174 } else if (mRightBorderActive) {
175 lp.width = mBaselineWidth + mDeltaX;
176 }
177
178 if (mTopBorderActive) {
179 lp.y = mBaselineY + mDeltaY;
180 lp.height = mBaselineHeight - mDeltaY;
181 } else if (mBottomBorderActive) {
182 lp.height = mBaselineHeight + mDeltaY;
183 }
184
185 resizeWidgetIfNeeded();
186 requestLayout();
187 }
188
Adam Cohen1b607ed2011-03-03 17:26:50 -0800189 /**
190 * Based on the current deltas, we determine if and how to resize the widget.
191 */
Adam Cohend4844c32011-02-18 19:25:06 -0800192 private void resizeWidgetIfNeeded() {
Adam Cohend4844c32011-02-18 19:25:06 -0800193 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
194 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
195
196 int hSpanInc = (int) Math.round(1.0f * mDeltaX / xThreshold) - mRunningHInc;
197 int vSpanInc = (int) Math.round(1.0f * mDeltaY / yThreshold) - mRunningVInc;
198 int cellXInc = 0;
199 int cellYInc = 0;
200
201 if (hSpanInc == 0 && vSpanInc == 0) return;
202
203 // Before we change the widget, we clear the occupied cells associated with it.
204 // The new set of occupied cells is marked below, once the layout params are updated.
205 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
206
207 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800208
209 // For each border, we bound the resizing based on the minimum width, and the maximum
210 // expandability.
Adam Cohend4844c32011-02-18 19:25:06 -0800211 if (mLeftBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800212 cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800213 cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
214 hSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800215 hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800216 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
217 mRunningHInc -= hSpanInc;
218 } else if (mRightBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800219 hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800220 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
221 mRunningHInc += hSpanInc;
222 }
223
224 if (mTopBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800225 cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800226 cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
227 vSpanInc *= -1;
Adam Cohen1b607ed2011-03-03 17:26:50 -0800228 vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800229 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
230 mRunningVInc -= vSpanInc;
231 } else if (mBottomBorderActive) {
Adam Cohen1b607ed2011-03-03 17:26:50 -0800232 vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
Adam Cohend4844c32011-02-18 19:25:06 -0800233 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
234 mRunningVInc += vSpanInc;
235 }
236
237 // Update the widget's dimensions and position according to the deltas computed above
238 if (mLeftBorderActive || mRightBorderActive) {
239 lp.cellHSpan += hSpanInc;
240 lp.cellX += cellXInc;
241 }
242
243 if (mTopBorderActive || mBottomBorderActive) {
244 lp.cellVSpan += vSpanInc;
245 lp.cellY += cellYInc;
246 }
247
Adam Cohen1b607ed2011-03-03 17:26:50 -0800248 // Update the expandability array, as we have changed the widget's size.
Adam Cohen3cba7222011-03-02 19:03:11 -0800249 mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
Adam Cohend4844c32011-02-18 19:25:06 -0800250
251 // Update the cells occupied by this widget
252 mCellLayout.markCellsAsOccupiedForView(mWidgetView);
253 }
254
Adam Cohen1b607ed2011-03-03 17:26:50 -0800255 /**
256 * This is the final step of the resize. Here we save the new widget size and position
257 * to LauncherModel and animate the resize frame.
258 */
Adam Cohend4844c32011-02-18 19:25:06 -0800259 public void commitResizeForDelta(int deltaX, int deltaY) {
260 visualizeResizeForDelta(deltaX, deltaY);
261
262 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
263 LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
264 lp.cellHSpan, lp.cellVSpan);
265 mWidgetView.requestLayout();
266
267 // Once our widget resizes (hence the post), we want to snap the resize frame to it
268 post(new Runnable() {
269 public void run() {
270 snapToWidget(true);
271 }
272 });
273 }
274
275 public void snapToWidget(boolean animate) {
276 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
277
Adam Cohen3cba7222011-03-02 19:03:11 -0800278 int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding;
279 int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding;
280 int newX = mWidgetView.getLeft() - mBackgroundPadding;
281 int newY = mWidgetView.getTop() - mBackgroundPadding;
282
283 // We need to make sure the frame stays within the bounds of the CellLayout
284 if (newY < 0) {
285 newHeight -= -newY;
286 newY = 0;
287 }
288 if (newY + newHeight > mCellLayout.getHeight()) {
289 newHeight -= newY + newHeight - mCellLayout.getHeight();
290 }
291
Adam Cohend4844c32011-02-18 19:25:06 -0800292 if (!animate) {
293 lp.width = newWidth;
294 lp.height = newHeight;
295 lp.x = newX;
296 lp.y = newY;
297 mLeftHandle.setAlpha(1.0f);
298 mRightHandle.setAlpha(1.0f);
299 mTopHandle.setAlpha(1.0f);
300 mBottomHandle.setAlpha(1.0f);
301 requestLayout();
302 } else {
303 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800304 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
305 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800306 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
307 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
308 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
309 ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
310 ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
311 ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
312 ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
313 oa.addUpdateListener(new AnimatorUpdateListener() {
314 public void onAnimationUpdate(ValueAnimator animation) {
315 requestLayout();
316 }
317 });
318 AnimatorSet set = new AnimatorSet();
319 if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
320 set.playTogether(oa, topOa, bottomOa);
321 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
322 set.playTogether(oa, leftOa, rightOa);
323 } else {
324 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
325 }
326
327 set.setDuration(SNAP_DURATION);
328 set.start();
329 }
330 }
331}