FolderIcon.java revision 3484638cad97e255a412b0489a63873fb3ca4218
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.animation.ValueAnimator.AnimatorUpdateListener; 23import android.content.Context; 24import android.content.res.Resources; 25import android.graphics.Canvas; 26import android.graphics.Color; 27import android.graphics.PorterDuff; 28import android.graphics.Rect; 29import android.graphics.drawable.Drawable; 30import android.os.Looper; 31import android.os.Parcelable; 32import android.util.AttributeSet; 33import android.view.LayoutInflater; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.ViewConfiguration; 37import android.view.ViewGroup; 38import android.view.animation.AccelerateInterpolator; 39import android.view.animation.DecelerateInterpolator; 40import android.widget.FrameLayout; 41import android.widget.ImageView; 42import android.widget.TextView; 43 44import com.android.launcher3.DropTarget.DragObject; 45import com.android.launcher3.FolderInfo.FolderListener; 46 47import java.util.ArrayList; 48 49/** 50 * An icon that can appear on in the workspace representing an {@link UserFolder}. 51 */ 52public class FolderIcon extends FrameLayout implements FolderListener { 53 private Launcher mLauncher; 54 private Folder mFolder; 55 private FolderInfo mInfo; 56 private static boolean sStaticValuesDirty = true; 57 58 private CheckLongPressHelper mLongPressHelper; 59 60 // The number of icons to display in the 61 private static final int NUM_ITEMS_IN_PREVIEW = 3; 62 private static final int CONSUMPTION_ANIMATION_DURATION = 100; 63 private static final int DROP_IN_ANIMATION_DURATION = 400; 64 private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; 65 private static final int FINAL_ITEM_ANIMATION_DURATION = 200; 66 67 // The degree to which the inner ring grows when accepting drop 68 private static final float INNER_RING_GROWTH_FACTOR = 0.15f; 69 70 // The degree to which the outer ring is scaled in its natural state 71 private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; 72 73 // The amount of vertical spread between items in the stack [0...1] 74 private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; 75 76 // Flag as to whether or not to draw an outer ring. Currently none is designed. 77 public static final boolean HAS_OUTER_RING = true; 78 79 // Flag whether the folder should open itself when an item is dragged over is enabled. 80 public static final boolean SPRING_LOADING_ENABLED = true; 81 82 // The degree to which the item in the back of the stack is scaled [0...1] 83 // (0 means it's not scaled at all, 1 means it's scaled to nothing) 84 private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; 85 86 // Delay when drag enters until the folder opens, in miliseconds. 87 private static final int ON_OPEN_DELAY = 800; 88 89 public static Drawable sSharedFolderLeaveBehind = null; 90 91 private ImageView mPreviewBackground; 92 private BubbleTextView mFolderName; 93 94 FolderRingAnimator mFolderRingAnimator = null; 95 96 // These variables are all associated with the drawing of the preview; they are stored 97 // as member variables for shared usage and to avoid computation on each frame 98 private int mIntrinsicIconSize; 99 private float mBaselineIconScale; 100 private int mBaselineIconSize; 101 private int mAvailableSpaceInPreview; 102 private int mTotalWidth = -1; 103 private int mPreviewOffsetX; 104 private int mPreviewOffsetY; 105 private float mMaxPerspectiveShift; 106 boolean mAnimating = false; 107 private Rect mOldBounds = new Rect(); 108 109 private float mSlop; 110 111 private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0); 112 private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); 113 private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>(); 114 115 private Alarm mOpenAlarm = new Alarm(); 116 private ItemInfo mDragInfo; 117 118 public FolderIcon(Context context, AttributeSet attrs) { 119 super(context, attrs); 120 init(); 121 } 122 123 public FolderIcon(Context context) { 124 super(context); 125 init(); 126 } 127 128 private void init() { 129 mLongPressHelper = new CheckLongPressHelper(this); 130 } 131 132 public boolean isDropEnabled() { 133 final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); 134 final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); 135 final Workspace workspace = (Workspace) cellLayout.getParent(); 136 return !workspace.isSmall(); 137 } 138 139 static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, 140 FolderInfo folderInfo, IconCache iconCache) { 141 @SuppressWarnings("all") // suppress dead code warning 142 final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; 143 if (error) { 144 throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " + 145 "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " + 146 "is dependent on this"); 147 } 148 LauncherAppState app = LauncherAppState.getInstance(); 149 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 150 151 FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); 152 icon.setClipToPadding(false); 153 icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); 154 icon.mFolderName.setText(folderInfo.title); 155 icon.mFolderName.setCompoundDrawablePadding(0); 156 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams(); 157 lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; 158 159 // Offset the preview background to center this view accordingly 160 icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); 161 lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams(); 162 lp.topMargin = grid.folderBackgroundOffset; 163 lp.width = grid.folderIconSizePx; 164 lp.height = grid.folderIconSizePx; 165 166 icon.setTag(folderInfo); 167 icon.setOnClickListener(launcher); 168 icon.mInfo = folderInfo; 169 icon.mLauncher = launcher; 170 icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), 171 folderInfo.title)); 172 Folder folder = Folder.fromXml(launcher); 173 folder.setDragController(launcher.getDragController()); 174 folder.setFolderIcon(icon); 175 folder.bind(folderInfo); 176 icon.mFolder = folder; 177 178 icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); 179 folderInfo.addListener(icon); 180 181 return icon; 182 } 183 184 @Override 185 protected Parcelable onSaveInstanceState() { 186 sStaticValuesDirty = true; 187 return super.onSaveInstanceState(); 188 } 189 190 public static class FolderRingAnimator { 191 public int mCellX; 192 public int mCellY; 193 private CellLayout mCellLayout; 194 public float mOuterRingSize; 195 public float mInnerRingSize; 196 public FolderIcon mFolderIcon = null; 197 public static Drawable sSharedOuterRingDrawable = null; 198 public static Drawable sSharedInnerRingDrawable = null; 199 public static int sPreviewSize = -1; 200 public static int sPreviewPadding = -1; 201 202 private ValueAnimator mAcceptAnimator; 203 private ValueAnimator mNeutralAnimator; 204 205 public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { 206 mFolderIcon = folderIcon; 207 Resources res = launcher.getResources(); 208 209 // We need to reload the static values when configuration changes in case they are 210 // different in another configuration 211 if (sStaticValuesDirty) { 212 if (Looper.myLooper() != Looper.getMainLooper()) { 213 throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread " 214 + Thread.currentThread()); 215 } 216 217 LauncherAppState app = LauncherAppState.getInstance(); 218 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 219 sPreviewSize = grid.folderIconSizePx; 220 sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); 221 sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); 222 sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip_holo); 223 sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); 224 sStaticValuesDirty = false; 225 } 226 } 227 228 public void animateToAcceptState() { 229 if (mNeutralAnimator != null) { 230 mNeutralAnimator.cancel(); 231 } 232 mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); 233 mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); 234 235 final int previewSize = sPreviewSize; 236 mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { 237 public void onAnimationUpdate(ValueAnimator animation) { 238 final float percent = (Float) animation.getAnimatedValue(); 239 mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; 240 mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; 241 if (mCellLayout != null) { 242 mCellLayout.invalidate(); 243 } 244 } 245 }); 246 mAcceptAnimator.addListener(new AnimatorListenerAdapter() { 247 @Override 248 public void onAnimationStart(Animator animation) { 249 if (mFolderIcon != null) { 250 mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); 251 } 252 } 253 }); 254 mAcceptAnimator.start(); 255 } 256 257 public void animateToNaturalState() { 258 if (mAcceptAnimator != null) { 259 mAcceptAnimator.cancel(); 260 } 261 mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); 262 mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); 263 264 final int previewSize = sPreviewSize; 265 mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { 266 public void onAnimationUpdate(ValueAnimator animation) { 267 final float percent = (Float) animation.getAnimatedValue(); 268 mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; 269 mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; 270 if (mCellLayout != null) { 271 mCellLayout.invalidate(); 272 } 273 } 274 }); 275 mNeutralAnimator.addListener(new AnimatorListenerAdapter() { 276 @Override 277 public void onAnimationEnd(Animator animation) { 278 if (mCellLayout != null) { 279 mCellLayout.hideFolderAccept(FolderRingAnimator.this); 280 } 281 if (mFolderIcon != null) { 282 mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); 283 } 284 } 285 }); 286 mNeutralAnimator.start(); 287 } 288 289 // Location is expressed in window coordinates 290 public void getCell(int[] loc) { 291 loc[0] = mCellX; 292 loc[1] = mCellY; 293 } 294 295 // Location is expressed in window coordinates 296 public void setCell(int x, int y) { 297 mCellX = x; 298 mCellY = y; 299 } 300 301 public void setCellLayout(CellLayout layout) { 302 mCellLayout = layout; 303 } 304 305 public float getOuterRingSize() { 306 return mOuterRingSize; 307 } 308 309 public float getInnerRingSize() { 310 return mInnerRingSize; 311 } 312 } 313 314 public Folder getFolder() { 315 return mFolder; 316 } 317 318 FolderInfo getFolderInfo() { 319 return mInfo; 320 } 321 322 private boolean willAcceptItem(ItemInfo item) { 323 final int itemType = item.itemType; 324 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 325 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 326 !mFolder.isFull() && item != mInfo && !mInfo.opened); 327 } 328 329 public boolean acceptDrop(Object dragInfo) { 330 final ItemInfo item = (ItemInfo) dragInfo; 331 return !mFolder.isDestroyed() && willAcceptItem(item); 332 } 333 334 public void addItem(ShortcutInfo item) { 335 mInfo.add(item); 336 } 337 338 public void onDragEnter(Object dragInfo) { 339 if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return; 340 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 341 CellLayout layout = (CellLayout) getParent().getParent(); 342 mFolderRingAnimator.setCell(lp.cellX, lp.cellY); 343 mFolderRingAnimator.setCellLayout(layout); 344 mFolderRingAnimator.animateToAcceptState(); 345 layout.showFolderAccept(mFolderRingAnimator); 346 mOpenAlarm.setOnAlarmListener(mOnOpenListener); 347 if (SPRING_LOADING_ENABLED && 348 ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { 349 // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even 350 // though widget-style shortcuts can be added to folders. The issue is that we need 351 // to deal with configuration activities which are currently handled in 352 // Workspace#onDropExternal. 353 mOpenAlarm.setAlarm(ON_OPEN_DELAY); 354 } 355 mDragInfo = (ItemInfo) dragInfo; 356 } 357 358 public void onDragOver(Object dragInfo) { 359 } 360 361 OnAlarmListener mOnOpenListener = new OnAlarmListener() { 362 public void onAlarm(Alarm alarm) { 363 ShortcutInfo item; 364 if (mDragInfo instanceof AppInfo) { 365 // Came from all apps -- make a copy. 366 item = ((AppInfo) mDragInfo).makeShortcut(); 367 item.spanX = 1; 368 item.spanY = 1; 369 } else { 370 // ShortcutInfo 371 item = (ShortcutInfo) mDragInfo; 372 } 373 mFolder.beginExternalDrag(item); 374 mLauncher.openFolder(FolderIcon.this); 375 } 376 }; 377 378 public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, 379 final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, 380 float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { 381 382 // These correspond two the drawable and view that the icon was dropped _onto_ 383 Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1]; 384 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 385 destView.getMeasuredWidth()); 386 387 // This will animate the first item from it's position as an icon into its 388 // position as the first item in the preview 389 animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null); 390 addItem(destInfo); 391 392 // This will animate the dragView (srcView) into the new folder 393 onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); 394 } 395 396 public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { 397 Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1]; 398 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 399 finalView.getMeasuredWidth()); 400 401 // This will animate the first item from it's position as an icon into its 402 // position as the first item in the preview 403 animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true, 404 onCompleteRunnable); 405 } 406 407 public void onDragExit(Object dragInfo) { 408 onDragExit(); 409 } 410 411 public void onDragExit() { 412 mFolderRingAnimator.animateToNaturalState(); 413 mOpenAlarm.cancelAlarm(); 414 } 415 416 private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, 417 float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, 418 DragObject d) { 419 item.cellX = -1; 420 item.cellY = -1; 421 422 // Typically, the animateView corresponds to the DragView; however, if this is being done 423 // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we 424 // will not have a view to animate 425 if (animateView != null) { 426 DragLayer dragLayer = mLauncher.getDragLayer(); 427 Rect from = new Rect(); 428 dragLayer.getViewRectRelativeToSelf(animateView, from); 429 Rect to = finalRect; 430 if (to == null) { 431 to = new Rect(); 432 Workspace workspace = mLauncher.getWorkspace(); 433 // Set cellLayout and this to it's final state to compute final animation locations 434 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); 435 float scaleX = getScaleX(); 436 float scaleY = getScaleY(); 437 setScaleX(1.0f); 438 setScaleY(1.0f); 439 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); 440 // Finished computing final animation locations, restore current state 441 setScaleX(scaleX); 442 setScaleY(scaleY); 443 workspace.resetTransitionTransform((CellLayout) getParent().getParent()); 444 } 445 446 int[] center = new int[2]; 447 float scale = getLocalCenterForIndex(index, center); 448 center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); 449 center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); 450 451 to.offset(center[0] - animateView.getMeasuredWidth() / 2, 452 center[1] - animateView.getMeasuredHeight() / 2); 453 454 float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; 455 456 float finalScale = scale * scaleRelativeToDragLayer; 457 dragLayer.animateView(animateView, from, to, finalAlpha, 458 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, 459 new DecelerateInterpolator(2), new AccelerateInterpolator(2), 460 postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); 461 addItem(item); 462 mHiddenItems.add(item); 463 mFolder.hideItem(item); 464 postDelayed(new Runnable() { 465 public void run() { 466 mHiddenItems.remove(item); 467 mFolder.showItem(item); 468 invalidate(); 469 } 470 }, DROP_IN_ANIMATION_DURATION); 471 } else { 472 addItem(item); 473 } 474 } 475 476 public void onDrop(DragObject d) { 477 ShortcutInfo item; 478 if (d.dragInfo instanceof AppInfo) { 479 // Came from all apps -- make a copy 480 item = ((AppInfo) d.dragInfo).makeShortcut(); 481 } else { 482 item = (ShortcutInfo) d.dragInfo; 483 } 484 mFolder.notifyDrop(); 485 onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); 486 } 487 488 private void computePreviewDrawingParams(int drawableSize, int totalSize) { 489 if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { 490 LauncherAppState app = LauncherAppState.getInstance(); 491 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 492 493 mIntrinsicIconSize = drawableSize; 494 mTotalWidth = totalSize; 495 496 final int previewSize = mPreviewBackground.getLayoutParams().height; 497 final int previewPadding = FolderRingAnimator.sPreviewPadding; 498 499 mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); 500 // cos(45) = 0.707 + ~= 0.1) = 0.8f 501 int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); 502 503 int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); 504 505 mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); 506 507 mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); 508 mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; 509 510 mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; 511 mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset; 512 } 513 } 514 515 private void computePreviewDrawingParams(Drawable d) { 516 computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); 517 } 518 519 class PreviewItemDrawingParams { 520 PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) { 521 this.transX = transX; 522 this.transY = transY; 523 this.scale = scale; 524 this.overlayAlpha = overlayAlpha; 525 } 526 float transX; 527 float transY; 528 float scale; 529 int overlayAlpha; 530 Drawable drawable; 531 } 532 533 private float getLocalCenterForIndex(int index, int[] center) { 534 mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); 535 536 mParams.transX += mPreviewOffsetX; 537 mParams.transY += mPreviewOffsetY; 538 float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; 539 float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; 540 541 center[0] = (int) Math.round(offsetX); 542 center[1] = (int) Math.round(offsetY); 543 return mParams.scale; 544 } 545 546 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, 547 PreviewItemDrawingParams params) { 548 index = NUM_ITEMS_IN_PREVIEW - index - 1; 549 float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); 550 float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); 551 552 float offset = (1 - r) * mMaxPerspectiveShift; 553 float scaledSize = scale * mBaselineIconSize; 554 float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; 555 556 // We want to imagine our coordinates from the bottom left, growing up and to the 557 // right. This is natural for the x-axis, but for the y-axis, we have to invert things. 558 float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop(); 559 float transX = (mAvailableSpaceInPreview - scaledSize) / 2; 560 float totalScale = mBaselineIconScale * scale; 561 final int overlayAlpha = (int) (80 * (1 - r)); 562 563 if (params == null) { 564 params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); 565 } else { 566 params.transX = transX; 567 params.transY = transY; 568 params.scale = totalScale; 569 params.overlayAlpha = overlayAlpha; 570 } 571 return params; 572 } 573 574 private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { 575 canvas.save(); 576 canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); 577 canvas.scale(params.scale, params.scale); 578 Drawable d = params.drawable; 579 580 if (d != null) { 581 mOldBounds.set(d.getBounds()); 582 d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); 583 d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255), 584 PorterDuff.Mode.SRC_ATOP); 585 d.draw(canvas); 586 d.clearColorFilter(); 587 d.setBounds(mOldBounds); 588 } 589 canvas.restore(); 590 } 591 592 @Override 593 protected void dispatchDraw(Canvas canvas) { 594 super.dispatchDraw(canvas); 595 596 if (mFolder == null) return; 597 if (mFolder.getItemCount() == 0 && !mAnimating) return; 598 599 ArrayList<View> items = mFolder.getItemsInReadingOrder(); 600 Drawable d; 601 TextView v; 602 603 // Update our drawing parameters if necessary 604 if (mAnimating) { 605 computePreviewDrawingParams(mAnimParams.drawable); 606 } else { 607 v = (TextView) items.get(0); 608 d = getTopDrawable(v); 609 computePreviewDrawingParams(d); 610 } 611 612 int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); 613 if (!mAnimating) { 614 for (int i = nItemsInPreview - 1; i >= 0; i--) { 615 v = (TextView) items.get(i); 616 if (!mHiddenItems.contains(v.getTag())) { 617 d = getTopDrawable(v); 618 mParams = computePreviewItemDrawingParams(i, mParams); 619 mParams.drawable = d; 620 drawPreviewItem(canvas, mParams); 621 } 622 } 623 } else { 624 drawPreviewItem(canvas, mAnimParams); 625 } 626 } 627 628 private Drawable getTopDrawable(TextView v) { 629 Drawable d = v.getCompoundDrawables()[1]; 630 return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; 631 } 632 633 private void animateFirstItem(final Drawable d, int duration, final boolean reverse, 634 final Runnable onCompleteRunnable) { 635 final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); 636 637 final float scale0 = 1.0f; 638 final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; 639 final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop(); 640 mAnimParams.drawable = d; 641 642 ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); 643 va.addUpdateListener(new AnimatorUpdateListener(){ 644 public void onAnimationUpdate(ValueAnimator animation) { 645 float progress = (Float) animation.getAnimatedValue(); 646 if (reverse) { 647 progress = 1 - progress; 648 mPreviewBackground.setAlpha(progress); 649 } 650 651 mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); 652 mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); 653 mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); 654 invalidate(); 655 } 656 }); 657 va.addListener(new AnimatorListenerAdapter() { 658 @Override 659 public void onAnimationStart(Animator animation) { 660 mAnimating = true; 661 } 662 @Override 663 public void onAnimationEnd(Animator animation) { 664 mAnimating = false; 665 if (onCompleteRunnable != null) { 666 onCompleteRunnable.run(); 667 } 668 } 669 }); 670 va.setDuration(duration); 671 va.start(); 672 } 673 674 public void setTextVisible(boolean visible) { 675 if (visible) { 676 mFolderName.setVisibility(VISIBLE); 677 } else { 678 mFolderName.setVisibility(INVISIBLE); 679 } 680 } 681 682 public boolean getTextVisible() { 683 return mFolderName.getVisibility() == VISIBLE; 684 } 685 686 public void onItemsChanged() { 687 invalidate(); 688 requestLayout(); 689 } 690 691 public void onAdd(ShortcutInfo item) { 692 invalidate(); 693 requestLayout(); 694 } 695 696 public void onRemove(ShortcutInfo item) { 697 invalidate(); 698 requestLayout(); 699 } 700 701 public void onTitleChanged(CharSequence title) { 702 mFolderName.setText(title.toString()); 703 setContentDescription(String.format(getContext().getString(R.string.folder_name_format), 704 title)); 705 } 706 707 @Override 708 public boolean onTouchEvent(MotionEvent event) { 709 // Call the superclass onTouchEvent first, because sometimes it changes the state to 710 // isPressed() on an ACTION_UP 711 boolean result = super.onTouchEvent(event); 712 713 switch (event.getAction()) { 714 case MotionEvent.ACTION_DOWN: 715 mLongPressHelper.postCheckForLongPress(); 716 break; 717 case MotionEvent.ACTION_CANCEL: 718 case MotionEvent.ACTION_UP: 719 mLongPressHelper.cancelLongPress(); 720 break; 721 case MotionEvent.ACTION_MOVE: 722 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { 723 mLongPressHelper.cancelLongPress(); 724 } 725 break; 726 } 727 return result; 728 } 729 730 @Override 731 protected void onAttachedToWindow() { 732 super.onAttachedToWindow(); 733 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 734 } 735 736 @Override 737 public void cancelLongPress() { 738 super.cancelLongPress(); 739 740 mLongPressHelper.cancelLongPress(); 741 } 742} 743