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