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