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