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 FolderIcon(Context context, AttributeSet attrs)115 public FolderIcon(Context context, AttributeSet attrs) { 116 super(context, attrs); 117 init(); 118 } 119 FolderIcon(Context context)120 public FolderIcon(Context context) { 121 super(context); 122 init(); 123 } 124 init()125 private void init() { 126 mLongPressHelper = new CheckLongPressHelper(this); 127 } 128 isDropEnabled()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 fromXml(int resId, Launcher launcher, ViewGroup group, FolderInfo folderInfo, IconCache iconCache)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 onSaveInstanceState()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 FolderRingAnimator(Launcher launcher, FolderIcon folderIcon)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 animateToAcceptState()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 animateToNaturalState()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 getCell(int[] loc)287 public void getCell(int[] loc) { 288 loc[0] = mCellX; 289 loc[1] = mCellY; 290 } 291 292 // Location is expressed in window coordinates setCell(int x, int y)293 public void setCell(int x, int y) { 294 mCellX = x; 295 mCellY = y; 296 } 297 setCellLayout(CellLayout layout)298 public void setCellLayout(CellLayout layout) { 299 mCellLayout = layout; 300 } 301 getOuterRingSize()302 public float getOuterRingSize() { 303 return mOuterRingSize; 304 } 305 getInnerRingSize()306 public float getInnerRingSize() { 307 return mInnerRingSize; 308 } 309 } 310 getFolder()311 Folder getFolder() { 312 return mFolder; 313 } 314 getFolderInfo()315 FolderInfo getFolderInfo() { 316 return mInfo; 317 } 318 willAcceptItem(ItemInfo item)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 acceptDrop(Object dragInfo)326 public boolean acceptDrop(Object dragInfo) { 327 final ItemInfo item = (ItemInfo) dragInfo; 328 return !mFolder.isDestroyed() && willAcceptItem(item); 329 } 330 addItem(ShortcutInfo item)331 public void addItem(ShortcutInfo item) { 332 mInfo.add(item); 333 } 334 onDragEnter(Object dragInfo)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 onDragOver(Object dragInfo)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 performCreateAnimation(final ShortcutInfo destInfo, final View destView, final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, float scaleRelativeToDragLayer, Runnable postAnimationRunnable)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 performDestroyAnimation(final View finalView, Runnable onCompleteRunnable)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 onDragExit(Object dragInfo)398 public void onDragExit(Object dragInfo) { 399 onDragExit(); 400 } 401 onDragExit()402 public void onDragExit() { 403 mFolderRingAnimator.animateToNaturalState(); 404 mOpenAlarm.cancelAlarm(); 405 } 406 onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, DragObject d)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 animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable)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 setTextVisible(boolean visible)659 public void setTextVisible(boolean visible) { 660 if (visible) { 661 mFolderName.setVisibility(VISIBLE); 662 } else { 663 mFolderName.setVisibility(INVISIBLE); 664 } 665 } 666 getTextVisible()667 public boolean getTextVisible() { 668 return mFolderName.getVisibility() == VISIBLE; 669 } 670 onItemsChanged()671 public void onItemsChanged() { 672 invalidate(); 673 requestLayout(); 674 } 675 onAdd(ShortcutInfo item)676 public void onAdd(ShortcutInfo item) { 677 invalidate(); 678 requestLayout(); 679 } 680 onRemove(ShortcutInfo item)681 public void onRemove(ShortcutInfo item) { 682 invalidate(); 683 requestLayout(); 684 } 685 onTitleChanged(CharSequence title)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 onTouchEvent(MotionEvent event)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 cancelLongPress()711 public void cancelLongPress() { 712 super.cancelLongPress(); 713 714 mLongPressHelper.cancelLongPress(); 715 } 716 } 717