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