AppWidgetResizeFrame.java revision 200358df3f91d7f18b70319918bbab0680ed21ee
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.AppWidgetHostView; 9import android.appwidget.AppWidgetProviderInfo; 10import android.content.Context; 11import android.graphics.Rect; 12import android.view.Gravity; 13import android.widget.FrameLayout; 14import android.widget.ImageView; 15 16import com.android.launcher.R; 17 18public class AppWidgetResizeFrame extends FrameLayout { 19 20 private ItemInfo mItemInfo; 21 private LauncherAppWidgetHostView mWidgetView; 22 private CellLayout mCellLayout; 23 private DragLayer mDragLayer; 24 private Workspace mWorkspace; 25 private ImageView mLeftHandle; 26 private ImageView mRightHandle; 27 private ImageView mTopHandle; 28 private ImageView mBottomHandle; 29 30 private boolean mLeftBorderActive; 31 private boolean mRightBorderActive; 32 private boolean mTopBorderActive; 33 private boolean mBottomBorderActive; 34 35 private int mWidgetPaddingLeft; 36 private int mWidgetPaddingRight; 37 private int mWidgetPaddingTop; 38 private int mWidgetPaddingBottom; 39 40 private int mBaselineWidth; 41 private int mBaselineHeight; 42 private int mBaselineX; 43 private int mBaselineY; 44 private int mResizeMode; 45 46 private int mRunningHInc; 47 private int mRunningVInc; 48 private int mMinHSpan; 49 private int mMinVSpan; 50 private int mDeltaX; 51 private int mDeltaY; 52 private int mDeltaXAddOn; 53 private int mDeltaYAddOn; 54 55 private int mBackgroundPadding; 56 private int mTouchTargetWidth; 57 58 int[] mDirectionVector = new int[2]; 59 60 final int SNAP_DURATION = 150; 61 final int BACKGROUND_PADDING = 24; 62 final float DIMMED_HANDLE_ALPHA = 0f; 63 final float RESIZE_THRESHOLD = 0.66f; 64 65 public static final int LEFT = 0; 66 public static final int TOP = 1; 67 public static final int RIGHT = 2; 68 public static final int BOTTOM = 3; 69 70 private Launcher mLauncher; 71 72 public AppWidgetResizeFrame(Context context, ItemInfo itemInfo, 73 LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { 74 75 super(context); 76 mLauncher = (Launcher) context; 77 mItemInfo = itemInfo; 78 mCellLayout = cellLayout; 79 mWidgetView = widgetView; 80 mResizeMode = widgetView.getAppWidgetInfo().resizeMode; 81 mDragLayer = dragLayer; 82 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); 83 84 final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo(); 85 int[] result = mLauncher.getMinSpanForWidget(info); 86 mMinHSpan = result[0]; 87 mMinVSpan = result[1]; 88 89 setBackgroundResource(R.drawable.widget_resize_frame_holo); 90 setPadding(0, 0, 0, 0); 91 92 LayoutParams lp; 93 mLeftHandle = new ImageView(context); 94 mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left); 95 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 96 Gravity.LEFT | Gravity.CENTER_VERTICAL); 97 addView(mLeftHandle, lp); 98 99 mRightHandle = new ImageView(context); 100 mRightHandle.setImageResource(R.drawable.widget_resize_handle_right); 101 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 102 Gravity.RIGHT | Gravity.CENTER_VERTICAL); 103 addView(mRightHandle, lp); 104 105 mTopHandle = new ImageView(context); 106 mTopHandle.setImageResource(R.drawable.widget_resize_handle_top); 107 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 108 Gravity.CENTER_HORIZONTAL | Gravity.TOP); 109 addView(mTopHandle, lp); 110 111 mBottomHandle = new ImageView(context); 112 mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom); 113 lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 114 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); 115 addView(mBottomHandle, lp); 116 117 Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context, 118 widgetView.getAppWidgetInfo().provider, null); 119 mWidgetPaddingLeft = p.left; 120 mWidgetPaddingTop = p.top; 121 mWidgetPaddingRight = p.right; 122 mWidgetPaddingBottom = p.bottom; 123 124 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { 125 mTopHandle.setVisibility(GONE); 126 mBottomHandle.setVisibility(GONE); 127 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { 128 mLeftHandle.setVisibility(GONE); 129 mRightHandle.setVisibility(GONE); 130 } 131 132 final float density = mLauncher.getResources().getDisplayMetrics().density; 133 mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING); 134 mTouchTargetWidth = 2 * mBackgroundPadding; 135 136 // When we create the resize frame, we first mark all cells as unoccupied. The appropriate 137 // cells (same if not resized, or different) will be marked as occupied when the resize 138 // frame is dismissed. 139 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); 140 } 141 142 public boolean beginResizeIfPointInRegion(int x, int y) { 143 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; 144 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; 145 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive; 146 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive; 147 mTopBorderActive = (y < mTouchTargetWidth) && verticalActive; 148 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive; 149 150 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive 151 || mTopBorderActive || mBottomBorderActive; 152 153 mBaselineWidth = getMeasuredWidth(); 154 mBaselineHeight = getMeasuredHeight(); 155 mBaselineX = getLeft(); 156 mBaselineY = getTop(); 157 158 if (anyBordersActive) { 159 mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); 160 mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA); 161 mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); 162 mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); 163 } 164 return anyBordersActive; 165 } 166 167 /** 168 * Here we bound the deltas such that the frame cannot be stretched beyond the extents 169 * of the CellLayout, and such that the frame's borders can't cross. 170 */ 171 public void updateDeltas(int deltaX, int deltaY) { 172 if (mLeftBorderActive) { 173 mDeltaX = Math.max(-mBaselineX, deltaX); 174 mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX); 175 } else if (mRightBorderActive) { 176 mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX); 177 mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX); 178 } 179 180 if (mTopBorderActive) { 181 mDeltaY = Math.max(-mBaselineY, deltaY); 182 mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY); 183 } else if (mBottomBorderActive) { 184 mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY); 185 mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY); 186 } 187 } 188 189 public void visualizeResizeForDelta(int deltaX, int deltaY) { 190 visualizeResizeForDelta(deltaX, deltaY, false); 191 } 192 193 /** 194 * Based on the deltas, we resize the frame, and, if needed, we resize the widget. 195 */ 196 private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) { 197 updateDeltas(deltaX, deltaY); 198 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 199 200 if (mLeftBorderActive) { 201 lp.x = mBaselineX + mDeltaX; 202 lp.width = mBaselineWidth - mDeltaX; 203 } else if (mRightBorderActive) { 204 lp.width = mBaselineWidth + mDeltaX; 205 } 206 207 if (mTopBorderActive) { 208 lp.y = mBaselineY + mDeltaY; 209 lp.height = mBaselineHeight - mDeltaY; 210 } else if (mBottomBorderActive) { 211 lp.height = mBaselineHeight + mDeltaY; 212 } 213 214 resizeWidgetIfNeeded(onDismiss); 215 requestLayout(); 216 } 217 218 /** 219 * Based on the current deltas, we determine if and how to resize the widget. 220 */ 221 private void resizeWidgetIfNeeded(boolean onDismiss) { 222 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); 223 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); 224 225 int deltaX = mDeltaX + mDeltaXAddOn; 226 int deltaY = mDeltaY + mDeltaYAddOn; 227 228 float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc; 229 float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc; 230 231 int hSpanInc = 0; 232 int vSpanInc = 0; 233 int cellXInc = 0; 234 int cellYInc = 0; 235 236 int countX = mCellLayout.getCountX(); 237 int countY = mCellLayout.getCountY(); 238 239 if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) { 240 hSpanInc = Math.round(hSpanIncF); 241 } 242 if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) { 243 vSpanInc = Math.round(vSpanIncF); 244 } 245 246 if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return; 247 248 249 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); 250 251 int spanX = lp.cellHSpan; 252 int spanY = lp.cellVSpan; 253 int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX; 254 int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY; 255 256 int hSpanDelta = 0; 257 int vSpanDelta = 0; 258 259 // For each border, we bound the resizing based on the minimum width, and the maximum 260 // expandability. 261 if (mLeftBorderActive) { 262 cellXInc = Math.max(-cellX, hSpanInc); 263 cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); 264 hSpanInc *= -1; 265 hSpanInc = Math.min(cellX, hSpanInc); 266 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); 267 hSpanDelta = -hSpanInc; 268 269 } else if (mRightBorderActive) { 270 hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc); 271 hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); 272 hSpanDelta = hSpanInc; 273 } 274 275 if (mTopBorderActive) { 276 cellYInc = Math.max(-cellY, vSpanInc); 277 cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); 278 vSpanInc *= -1; 279 vSpanInc = Math.min(cellY, vSpanInc); 280 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); 281 vSpanDelta = -vSpanInc; 282 } else if (mBottomBorderActive) { 283 vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc); 284 vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); 285 vSpanDelta = vSpanInc; 286 } 287 288 mDirectionVector[0] = 0; 289 mDirectionVector[1] = 0; 290 // Update the widget's dimensions and position according to the deltas computed above 291 if (mLeftBorderActive || mRightBorderActive) { 292 spanX += hSpanInc; 293 cellX += cellXInc; 294 mDirectionVector[0] = mLeftBorderActive ? -1 : 1; 295 } 296 297 if (mTopBorderActive || mBottomBorderActive) { 298 spanY += vSpanInc; 299 cellY += cellYInc; 300 mDirectionVector[1] = mTopBorderActive ? -1 : 1; 301 } 302 303 if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return; 304 305 if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView, 306 mDirectionVector, onDismiss)) { 307 lp.tmpCellX = cellX; 308 lp.tmpCellY = cellY; 309 lp.cellHSpan = spanX; 310 lp.cellVSpan = spanY; 311 mRunningVInc += vSpanDelta; 312 mRunningHInc += hSpanDelta; 313 } 314 315 mWidgetView.requestLayout(); 316 } 317 318 /** 319 * This is the final step of the resize. Here we save the new widget size and position 320 * to LauncherModel and animate the resize frame. 321 */ 322 public void commitResize() { 323 resizeWidgetIfNeeded(true); 324 requestLayout(); 325 } 326 327 public void onTouchUp() { 328 int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); 329 int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); 330 331 mDeltaXAddOn = mRunningHInc * xThreshold; 332 mDeltaYAddOn = mRunningVInc * yThreshold; 333 mDeltaX = 0; 334 mDeltaY = 0; 335 336 post(new Runnable() { 337 @Override 338 public void run() { 339 snapToWidget(true); 340 } 341 }); 342 } 343 344 public void snapToWidget(boolean animate) { 345 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 346 int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX(); 347 int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY(); 348 349 int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft - 350 mWidgetPaddingRight; 351 int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop - 352 mWidgetPaddingBottom; 353 354 int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft; 355 int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop; 356 357 // We need to make sure the frame stays within the bounds of the CellLayout 358 if (newY < 0) { 359 newHeight -= -newY; 360 newY = 0; 361 } 362 if (newY + newHeight > mDragLayer.getHeight()) { 363 newHeight -= newY + newHeight - mDragLayer.getHeight(); 364 } 365 366 if (!animate) { 367 lp.width = newWidth; 368 lp.height = newHeight; 369 lp.x = newX; 370 lp.y = newY; 371 mLeftHandle.setAlpha(1.0f); 372 mRightHandle.setAlpha(1.0f); 373 mTopHandle.setAlpha(1.0f); 374 mBottomHandle.setAlpha(1.0f); 375 requestLayout(); 376 } else { 377 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth); 378 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height, 379 newHeight); 380 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX); 381 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); 382 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 383 ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f); 384 ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f); 385 ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f); 386 ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f); 387 oa.addUpdateListener(new AnimatorUpdateListener() { 388 public void onAnimationUpdate(ValueAnimator animation) { 389 requestLayout(); 390 } 391 }); 392 AnimatorSet set = new AnimatorSet(); 393 if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { 394 set.playTogether(oa, topOa, bottomOa); 395 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { 396 set.playTogether(oa, leftOa, rightOa); 397 } else { 398 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa); 399 } 400 401 set.setDuration(SNAP_DURATION); 402 set.start(); 403 } 404 } 405} 406