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