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 FolderIcon(Context context, AttributeSet attrs)118 public FolderIcon(Context context, AttributeSet attrs) { 119 super(context, attrs); 120 init(); 121 } 122 FolderIcon(Context context)123 public FolderIcon(Context context) { 124 super(context); 125 init(); 126 } 127 init()128 private void init() { 129 mLongPressHelper = new CheckLongPressHelper(this); 130 } 131 isDropEnabled()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 fromXml(int resId, Launcher launcher, ViewGroup group, FolderInfo folderInfo, IconCache iconCache)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 onSaveInstanceState()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 FolderRingAnimator(Launcher launcher, FolderIcon folderIcon)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 animateToAcceptState()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 animateToNaturalState()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 getCell(int[] loc)291 public void getCell(int[] loc) { 292 loc[0] = mCellX; 293 loc[1] = mCellY; 294 } 295 296 // Location is expressed in window coordinates setCell(int x, int y)297 public void setCell(int x, int y) { 298 mCellX = x; 299 mCellY = y; 300 } 301 setCellLayout(CellLayout layout)302 public void setCellLayout(CellLayout layout) { 303 mCellLayout = layout; 304 } 305 getOuterRingSize()306 public float getOuterRingSize() { 307 return mOuterRingSize; 308 } 309 getInnerRingSize()310 public float getInnerRingSize() { 311 return mInnerRingSize; 312 } 313 } 314 getFolder()315 public Folder getFolder() { 316 return mFolder; 317 } 318 getFolderInfo()319 FolderInfo getFolderInfo() { 320 return mInfo; 321 } 322 willAcceptItem(ItemInfo item)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 acceptDrop(Object dragInfo)330 public boolean acceptDrop(Object dragInfo) { 331 final ItemInfo item = (ItemInfo) dragInfo; 332 return !mFolder.isDestroyed() && willAcceptItem(item); 333 } 334 addItem(ShortcutInfo item)335 public void addItem(ShortcutInfo item) { 336 mInfo.add(item); 337 } 338 onDragEnter(Object dragInfo)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 onDragOver(Object dragInfo)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 performCreateAnimation(final ShortcutInfo destInfo, final View destView, final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, float scaleRelativeToDragLayer, Runnable postAnimationRunnable)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 performDestroyAnimation(final View finalView, Runnable onCompleteRunnable)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 onDragExit(Object dragInfo)408 public void onDragExit(Object dragInfo) { 409 onDragExit(); 410 } 411 onDragExit()412 public void onDragExit() { 413 mFolderRingAnimator.animateToNaturalState(); 414 mOpenAlarm.cancelAlarm(); 415 } 416 onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, DragObject d)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 getTopDrawable(TextView v)637 private Drawable getTopDrawable(TextView v) { 638 Drawable d = v.getCompoundDrawables()[1]; 639 return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; 640 } 641 animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable)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 setTextVisible(boolean visible)683 public void setTextVisible(boolean visible) { 684 if (visible) { 685 mFolderName.setVisibility(VISIBLE); 686 } else { 687 mFolderName.setVisibility(INVISIBLE); 688 } 689 } 690 getTextVisible()691 public boolean getTextVisible() { 692 return mFolderName.getVisibility() == VISIBLE; 693 } 694 onItemsChanged()695 public void onItemsChanged() { 696 invalidate(); 697 requestLayout(); 698 } 699 onAdd(ShortcutInfo item)700 public void onAdd(ShortcutInfo item) { 701 invalidate(); 702 requestLayout(); 703 } 704 onRemove(ShortcutInfo item)705 public void onRemove(ShortcutInfo item) { 706 invalidate(); 707 requestLayout(); 708 } 709 onTitleChanged(CharSequence title)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 onTouchEvent(MotionEvent event)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 onAttachedToWindow()740 protected void onAttachedToWindow() { 741 super.onAttachedToWindow(); 742 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 743 } 744 745 @Override cancelLongPress()746 public void cancelLongPress() { 747 super.cancelLongPress(); 748 749 mLongPressHelper.cancelLongPress(); 750 } 751 } 752