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