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.ViewConfiguration; 37 import android.view.ViewGroup; 38 import android.view.animation.AccelerateInterpolator; 39 import android.view.animation.DecelerateInterpolator; 40 import android.widget.FrameLayout; 41 import android.widget.ImageView; 42 import android.widget.TextView; 43 44 import com.android.launcher3.DropTarget.DragObject; 45 import com.android.launcher3.FolderInfo.FolderListener; 46 47 import java.util.ArrayList; 48 49 /** 50 * An icon that can appear on in the workspace representing an {@link UserFolder}. 51 */ 52 public 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.workspaceInModalState(); 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 icon.setOnFocusChangeListener(launcher.mFocusHandler); 182 return icon; 183 } 184 185 @Override 186 protected Parcelable onSaveInstanceState() { 187 sStaticValuesDirty = true; 188 return super.onSaveInstanceState(); 189 } 190 191 public static class FolderRingAnimator { 192 public int mCellX; 193 public int mCellY; 194 private CellLayout mCellLayout; 195 public float mOuterRingSize; 196 public float mInnerRingSize; 197 public FolderIcon mFolderIcon = null; 198 public static Drawable sSharedOuterRingDrawable = null; 199 public static Drawable sSharedInnerRingDrawable = null; 200 public static int sPreviewSize = -1; 201 public static int sPreviewPadding = -1; 202 203 private ValueAnimator mAcceptAnimator; 204 private ValueAnimator mNeutralAnimator; 205 206 public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { 207 mFolderIcon = folderIcon; 208 Resources res = launcher.getResources(); 209 210 // We need to reload the static values when configuration changes in case they are 211 // different in another configuration 212 if (sStaticValuesDirty) { 213 if (Looper.myLooper() != Looper.getMainLooper()) { 214 throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread " 215 + Thread.currentThread()); 216 } 217 218 LauncherAppState app = LauncherAppState.getInstance(); 219 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 220 sPreviewSize = grid.folderIconSizePx; 221 sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); 222 sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); 223 sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip_holo); 224 sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); 225 sStaticValuesDirty = false; 226 } 227 } 228 229 public void animateToAcceptState() { 230 if (mNeutralAnimator != null) { 231 mNeutralAnimator.cancel(); 232 } 233 mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); 234 mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); 235 236 final int previewSize = sPreviewSize; 237 mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { 238 public void onAnimationUpdate(ValueAnimator animation) { 239 final float percent = (Float) animation.getAnimatedValue(); 240 mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; 241 mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; 242 if (mCellLayout != null) { 243 mCellLayout.invalidate(); 244 } 245 } 246 }); 247 mAcceptAnimator.addListener(new AnimatorListenerAdapter() { 248 @Override 249 public void onAnimationStart(Animator animation) { 250 if (mFolderIcon != null) { 251 mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); 252 } 253 } 254 }); 255 mAcceptAnimator.start(); 256 } 257 258 public void animateToNaturalState() { 259 if (mAcceptAnimator != null) { 260 mAcceptAnimator.cancel(); 261 } 262 mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); 263 mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); 264 265 final int previewSize = sPreviewSize; 266 mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { 267 public void onAnimationUpdate(ValueAnimator animation) { 268 final float percent = (Float) animation.getAnimatedValue(); 269 mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; 270 mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; 271 if (mCellLayout != null) { 272 mCellLayout.invalidate(); 273 } 274 } 275 }); 276 mNeutralAnimator.addListener(new AnimatorListenerAdapter() { 277 @Override 278 public void onAnimationEnd(Animator animation) { 279 if (mCellLayout != null) { 280 mCellLayout.hideFolderAccept(FolderRingAnimator.this); 281 } 282 if (mFolderIcon != null) { 283 mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); 284 } 285 } 286 }); 287 mNeutralAnimator.start(); 288 } 289 290 // Location is expressed in window coordinates 291 public void getCell(int[] loc) { 292 loc[0] = mCellX; 293 loc[1] = mCellY; 294 } 295 296 // Location is expressed in window coordinates 297 public void setCell(int x, int y) { 298 mCellX = x; 299 mCellY = y; 300 } 301 302 public void setCellLayout(CellLayout layout) { 303 mCellLayout = layout; 304 } 305 306 public float getOuterRingSize() { 307 return mOuterRingSize; 308 } 309 310 public float getInnerRingSize() { 311 return mInnerRingSize; 312 } 313 } 314 315 public Folder getFolder() { 316 return mFolder; 317 } 318 319 FolderInfo getFolderInfo() { 320 return mInfo; 321 } 322 323 private boolean willAcceptItem(ItemInfo item) { 324 final int itemType = item.itemType; 325 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 326 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 327 !mFolder.isFull() && item != mInfo && !mInfo.opened); 328 } 329 330 public boolean acceptDrop(Object dragInfo) { 331 final ItemInfo item = (ItemInfo) dragInfo; 332 return !mFolder.isDestroyed() && willAcceptItem(item); 333 } 334 335 public void addItem(ShortcutInfo item) { 336 mInfo.add(item); 337 } 338 339 public void onDragEnter(Object dragInfo) { 340 if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return; 341 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 342 CellLayout layout = (CellLayout) getParent().getParent(); 343 mFolderRingAnimator.setCell(lp.cellX, lp.cellY); 344 mFolderRingAnimator.setCellLayout(layout); 345 mFolderRingAnimator.animateToAcceptState(); 346 layout.showFolderAccept(mFolderRingAnimator); 347 mOpenAlarm.setOnAlarmListener(mOnOpenListener); 348 if (SPRING_LOADING_ENABLED && 349 ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { 350 // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even 351 // though widget-style shortcuts can be added to folders. The issue is that we need 352 // to deal with configuration activities which are currently handled in 353 // Workspace#onDropExternal. 354 mOpenAlarm.setAlarm(ON_OPEN_DELAY); 355 } 356 mDragInfo = (ItemInfo) dragInfo; 357 } 358 359 public void onDragOver(Object dragInfo) { 360 } 361 362 OnAlarmListener mOnOpenListener = new OnAlarmListener() { 363 public void onAlarm(Alarm alarm) { 364 ShortcutInfo item; 365 if (mDragInfo instanceof AppInfo) { 366 // Came from all apps -- make a copy. 367 item = ((AppInfo) mDragInfo).makeShortcut(); 368 item.spanX = 1; 369 item.spanY = 1; 370 } else { 371 // ShortcutInfo 372 item = (ShortcutInfo) mDragInfo; 373 } 374 mFolder.beginExternalDrag(item); 375 mLauncher.openFolder(FolderIcon.this); 376 } 377 }; 378 379 public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, 380 final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, 381 float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { 382 383 // These correspond two the drawable and view that the icon was dropped _onto_ 384 Drawable animateDrawable = getTopDrawable((TextView) destView); 385 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 386 destView.getMeasuredWidth()); 387 388 // This will animate the first item from it's position as an icon into its 389 // position as the first item in the preview 390 animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null); 391 addItem(destInfo); 392 393 // This will animate the dragView (srcView) into the new folder 394 onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); 395 } 396 397 public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { 398 Drawable animateDrawable = getTopDrawable((TextView) finalView); 399 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 400 finalView.getMeasuredWidth()); 401 402 // This will animate the first item from it's position as an icon into its 403 // position as the first item in the preview 404 animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true, 405 onCompleteRunnable); 406 } 407 408 public void onDragExit(Object dragInfo) { 409 onDragExit(); 410 } 411 412 public void onDragExit() { 413 mFolderRingAnimator.animateToNaturalState(); 414 mOpenAlarm.cancelAlarm(); 415 } 416 417 private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, 418 float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, 419 DragObject d) { 420 item.cellX = -1; 421 item.cellY = -1; 422 423 // Typically, the animateView corresponds to the DragView; however, if this is being done 424 // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we 425 // will not have a view to animate 426 if (animateView != null) { 427 DragLayer dragLayer = mLauncher.getDragLayer(); 428 Rect from = new Rect(); 429 dragLayer.getViewRectRelativeToSelf(animateView, from); 430 Rect to = finalRect; 431 if (to == null) { 432 to = new Rect(); 433 Workspace workspace = mLauncher.getWorkspace(); 434 // Set cellLayout and this to it's final state to compute final animation locations 435 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); 436 float scaleX = getScaleX(); 437 float scaleY = getScaleY(); 438 setScaleX(1.0f); 439 setScaleY(1.0f); 440 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); 441 // Finished computing final animation locations, restore current state 442 setScaleX(scaleX); 443 setScaleY(scaleY); 444 workspace.resetTransitionTransform((CellLayout) getParent().getParent()); 445 } 446 447 int[] center = new int[2]; 448 float scale = getLocalCenterForIndex(index, center); 449 center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); 450 center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); 451 452 to.offset(center[0] - animateView.getMeasuredWidth() / 2, 453 center[1] - animateView.getMeasuredHeight() / 2); 454 455 float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; 456 457 float finalScale = scale * scaleRelativeToDragLayer; 458 dragLayer.animateView(animateView, from, to, finalAlpha, 459 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, 460 new DecelerateInterpolator(2), new AccelerateInterpolator(2), 461 postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); 462 addItem(item); 463 mHiddenItems.add(item); 464 mFolder.hideItem(item); 465 postDelayed(new Runnable() { 466 public void run() { 467 mHiddenItems.remove(item); 468 mFolder.showItem(item); 469 invalidate(); 470 } 471 }, DROP_IN_ANIMATION_DURATION); 472 } else { 473 addItem(item); 474 } 475 } 476 477 public void onDrop(DragObject d) { 478 ShortcutInfo item; 479 if (d.dragInfo instanceof AppInfo) { 480 // Came from all apps -- make a copy 481 item = ((AppInfo) d.dragInfo).makeShortcut(); 482 } else { 483 item = (ShortcutInfo) d.dragInfo; 484 } 485 mFolder.notifyDrop(); 486 onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); 487 } 488 489 private void computePreviewDrawingParams(int drawableSize, int totalSize) { 490 if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { 491 LauncherAppState app = LauncherAppState.getInstance(); 492 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 493 494 mIntrinsicIconSize = drawableSize; 495 mTotalWidth = totalSize; 496 497 final int previewSize = mPreviewBackground.getLayoutParams().height; 498 final int previewPadding = FolderRingAnimator.sPreviewPadding; 499 500 mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); 501 // cos(45) = 0.707 + ~= 0.1) = 0.8f 502 int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); 503 504 int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); 505 506 mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); 507 508 mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); 509 mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; 510 511 mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; 512 mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset; 513 } 514 } 515 516 private void computePreviewDrawingParams(Drawable d) { 517 computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); 518 } 519 520 class PreviewItemDrawingParams { 521 PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) { 522 this.transX = transX; 523 this.transY = transY; 524 this.scale = scale; 525 this.overlayAlpha = overlayAlpha; 526 } 527 float transX; 528 float transY; 529 float scale; 530 int overlayAlpha; 531 Drawable drawable; 532 } 533 534 private float getLocalCenterForIndex(int index, int[] center) { 535 mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); 536 537 mParams.transX += mPreviewOffsetX; 538 mParams.transY += mPreviewOffsetY; 539 float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; 540 float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; 541 542 center[0] = (int) Math.round(offsetX); 543 center[1] = (int) Math.round(offsetY); 544 return mParams.scale; 545 } 546 547 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, 548 PreviewItemDrawingParams params) { 549 index = NUM_ITEMS_IN_PREVIEW - index - 1; 550 float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); 551 float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); 552 553 float offset = (1 - r) * mMaxPerspectiveShift; 554 float scaledSize = scale * mBaselineIconSize; 555 float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; 556 557 // We want to imagine our coordinates from the bottom left, growing up and to the 558 // right. This is natural for the x-axis, but for the y-axis, we have to invert things. 559 float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop(); 560 float transX = (mAvailableSpaceInPreview - scaledSize) / 2; 561 float totalScale = mBaselineIconScale * scale; 562 final int overlayAlpha = (int) (80 * (1 - r)); 563 564 if (params == null) { 565 params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); 566 } else { 567 params.transX = transX; 568 params.transY = transY; 569 params.scale = totalScale; 570 params.overlayAlpha = overlayAlpha; 571 } 572 return params; 573 } 574 575 private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { 576 canvas.save(); 577 canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); 578 canvas.scale(params.scale, params.scale); 579 Drawable d = params.drawable; 580 581 if (d != null) { 582 mOldBounds.set(d.getBounds()); 583 d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); 584 if (d instanceof FastBitmapDrawable) { 585 FastBitmapDrawable fd = (FastBitmapDrawable) d; 586 int oldBrightness = fd.getBrightness(); 587 fd.setBrightness(params.overlayAlpha); 588 d.draw(canvas); 589 fd.setBrightness(oldBrightness); 590 } else { 591 d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255), 592 PorterDuff.Mode.SRC_ATOP); 593 d.draw(canvas); 594 d.clearColorFilter(); 595 } 596 d.setBounds(mOldBounds); 597 } 598 canvas.restore(); 599 } 600 601 @Override 602 protected void dispatchDraw(Canvas canvas) { 603 super.dispatchDraw(canvas); 604 605 if (mFolder == null) return; 606 if (mFolder.getItemCount() == 0 && !mAnimating) return; 607 608 ArrayList<View> items = mFolder.getItemsInReadingOrder(); 609 Drawable d; 610 TextView v; 611 612 // Update our drawing parameters if necessary 613 if (mAnimating) { 614 computePreviewDrawingParams(mAnimParams.drawable); 615 } else { 616 v = (TextView) items.get(0); 617 d = getTopDrawable(v); 618 computePreviewDrawingParams(d); 619 } 620 621 int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); 622 if (!mAnimating) { 623 for (int i = nItemsInPreview - 1; i >= 0; i--) { 624 v = (TextView) items.get(i); 625 if (!mHiddenItems.contains(v.getTag())) { 626 d = getTopDrawable(v); 627 mParams = computePreviewItemDrawingParams(i, mParams); 628 mParams.drawable = d; 629 drawPreviewItem(canvas, mParams); 630 } 631 } 632 } else { 633 drawPreviewItem(canvas, mAnimParams); 634 } 635 } 636 637 private Drawable getTopDrawable(TextView v) { 638 Drawable d = v.getCompoundDrawables()[1]; 639 return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; 640 } 641 642 private void animateFirstItem(final Drawable d, int duration, final boolean reverse, 643 final Runnable onCompleteRunnable) { 644 final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); 645 646 final float scale0 = 1.0f; 647 final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; 648 final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop(); 649 mAnimParams.drawable = d; 650 651 ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); 652 va.addUpdateListener(new AnimatorUpdateListener(){ 653 public void onAnimationUpdate(ValueAnimator animation) { 654 float progress = (Float) animation.getAnimatedValue(); 655 if (reverse) { 656 progress = 1 - progress; 657 mPreviewBackground.setAlpha(progress); 658 } 659 660 mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); 661 mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); 662 mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); 663 invalidate(); 664 } 665 }); 666 va.addListener(new AnimatorListenerAdapter() { 667 @Override 668 public void onAnimationStart(Animator animation) { 669 mAnimating = true; 670 } 671 @Override 672 public void onAnimationEnd(Animator animation) { 673 mAnimating = false; 674 if (onCompleteRunnable != null) { 675 onCompleteRunnable.run(); 676 } 677 } 678 }); 679 va.setDuration(duration); 680 va.start(); 681 } 682 683 public void setTextVisible(boolean visible) { 684 if (visible) { 685 mFolderName.setVisibility(VISIBLE); 686 } else { 687 mFolderName.setVisibility(INVISIBLE); 688 } 689 } 690 691 public boolean getTextVisible() { 692 return mFolderName.getVisibility() == VISIBLE; 693 } 694 695 public void onItemsChanged() { 696 invalidate(); 697 requestLayout(); 698 } 699 700 public void onAdd(ShortcutInfo item) { 701 invalidate(); 702 requestLayout(); 703 } 704 705 public void onRemove(ShortcutInfo item) { 706 invalidate(); 707 requestLayout(); 708 } 709 710 public void onTitleChanged(CharSequence title) { 711 mFolderName.setText(title.toString()); 712 setContentDescription(String.format(getContext().getString(R.string.folder_name_format), 713 title)); 714 } 715 716 @Override 717 public boolean onTouchEvent(MotionEvent event) { 718 // Call the superclass onTouchEvent first, because sometimes it changes the state to 719 // isPressed() on an ACTION_UP 720 boolean result = super.onTouchEvent(event); 721 722 switch (event.getAction()) { 723 case MotionEvent.ACTION_DOWN: 724 mLongPressHelper.postCheckForLongPress(); 725 break; 726 case MotionEvent.ACTION_CANCEL: 727 case MotionEvent.ACTION_UP: 728 mLongPressHelper.cancelLongPress(); 729 break; 730 case MotionEvent.ACTION_MOVE: 731 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { 732 mLongPressHelper.cancelLongPress(); 733 } 734 break; 735 } 736 return result; 737 } 738 739 @Override 740 protected void onAttachedToWindow() { 741 super.onAttachedToWindow(); 742 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 743 } 744 745 @Override 746 public void cancelLongPress() { 747 super.cancelLongPress(); 748 749 mLongPressHelper.cancelLongPress(); 750 } 751 } 752