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 mFolder.hideItem(item); 406 postDelayed(new Runnable() { 407 public void run() { 408 mHiddenItems.remove(item); 409 mFolder.showItem(item); 410 invalidate(); 411 } 412 }, DROP_IN_ANIMATION_DURATION); 413 } else { 414 addItem(item); 415 } 416 } 417 418 public void onDrop(DragObject d) { 419 ShortcutInfo item; 420 if (d.dragInfo instanceof ApplicationInfo) { 421 // Came from all apps -- make a copy 422 item = ((ApplicationInfo) d.dragInfo).makeShortcut(); 423 } else { 424 item = (ShortcutInfo) d.dragInfo; 425 } 426 mFolder.notifyDrop(); 427 onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); 428 } 429 430 public DropTarget getDropTargetDelegate(DragObject d) { 431 return null; 432 } 433 434 private void computePreviewDrawingParams(int drawableSize, int totalSize) { 435 if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { 436 mIntrinsicIconSize = drawableSize; 437 mTotalWidth = totalSize; 438 439 final int previewSize = FolderRingAnimator.sPreviewSize; 440 final int previewPadding = FolderRingAnimator.sPreviewPadding; 441 442 mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); 443 // cos(45) = 0.707 + ~= 0.1) = 0.8f 444 int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); 445 446 int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); 447 mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); 448 449 mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); 450 mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; 451 452 mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; 453 mPreviewOffsetY = previewPadding; 454 } 455 } 456 457 private void computePreviewDrawingParams(Drawable d) { 458 computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); 459 } 460 461 class PreviewItemDrawingParams { 462 PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) { 463 this.transX = transX; 464 this.transY = transY; 465 this.scale = scale; 466 this.overlayAlpha = overlayAlpha; 467 } 468 float transX; 469 float transY; 470 float scale; 471 int overlayAlpha; 472 Drawable drawable; 473 } 474 475 private float getLocalCenterForIndex(int index, int[] center) { 476 mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); 477 478 mParams.transX += mPreviewOffsetX; 479 mParams.transY += mPreviewOffsetY; 480 float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; 481 float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; 482 483 center[0] = (int) Math.round(offsetX); 484 center[1] = (int) Math.round(offsetY); 485 return mParams.scale; 486 } 487 488 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, 489 PreviewItemDrawingParams params) { 490 index = NUM_ITEMS_IN_PREVIEW - index - 1; 491 float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); 492 float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); 493 494 float offset = (1 - r) * mMaxPerspectiveShift; 495 float scaledSize = scale * mBaselineIconSize; 496 float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; 497 498 // We want to imagine our coordinates from the bottom left, growing up and to the 499 // right. This is natural for the x-axis, but for the y-axis, we have to invert things. 500 float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection); 501 float transX = offset + scaleOffsetCorrection; 502 float totalScale = mBaselineIconScale * scale; 503 final int overlayAlpha = (int) (80 * (1 - r)); 504 505 if (params == null) { 506 params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); 507 } else { 508 params.transX = transX; 509 params.transY = transY; 510 params.scale = totalScale; 511 params.overlayAlpha = overlayAlpha; 512 } 513 return params; 514 } 515 516 private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { 517 canvas.save(); 518 canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); 519 canvas.scale(params.scale, params.scale); 520 Drawable d = params.drawable; 521 522 if (d != null) { 523 d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); 524 d.setFilterBitmap(true); 525 d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP); 526 d.draw(canvas); 527 d.clearColorFilter(); 528 d.setFilterBitmap(false); 529 } 530 canvas.restore(); 531 } 532 533 @Override 534 protected void dispatchDraw(Canvas canvas) { 535 super.dispatchDraw(canvas); 536 537 if (mFolder == null) return; 538 if (mFolder.getItemCount() == 0 && !mAnimating) return; 539 540 ArrayList<View> items = mFolder.getItemsInReadingOrder(); 541 Drawable d; 542 TextView v; 543 544 // Update our drawing parameters if necessary 545 if (mAnimating) { 546 computePreviewDrawingParams(mAnimParams.drawable); 547 } else { 548 v = (TextView) items.get(0); 549 d = v.getCompoundDrawables()[1]; 550 computePreviewDrawingParams(d); 551 } 552 553 int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); 554 if (!mAnimating) { 555 for (int i = nItemsInPreview - 1; i >= 0; i--) { 556 v = (TextView) items.get(i); 557 if (!mHiddenItems.contains(v.getTag())) { 558 d = v.getCompoundDrawables()[1]; 559 mParams = computePreviewItemDrawingParams(i, mParams); 560 mParams.drawable = d; 561 drawPreviewItem(canvas, mParams); 562 } 563 } 564 } else { 565 drawPreviewItem(canvas, mAnimParams); 566 } 567 } 568 animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable)569 private void animateFirstItem(final Drawable d, int duration, final boolean reverse, 570 final Runnable onCompleteRunnable) { 571 final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); 572 573 final float scale0 = 1.0f; 574 final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; 575 final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2; 576 mAnimParams.drawable = d; 577 578 ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); 579 va.addUpdateListener(new AnimatorUpdateListener(){ 580 public void onAnimationUpdate(ValueAnimator animation) { 581 float progress = (Float) animation.getAnimatedValue(); 582 if (reverse) { 583 progress = 1 - progress; 584 mPreviewBackground.setAlpha(progress); 585 } 586 587 mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); 588 mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); 589 mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); 590 invalidate(); 591 } 592 }); 593 va.addListener(new AnimatorListenerAdapter() { 594 @Override 595 public void onAnimationStart(Animator animation) { 596 mAnimating = true; 597 } 598 @Override 599 public void onAnimationEnd(Animator animation) { 600 mAnimating = false; 601 if (onCompleteRunnable != null) { 602 onCompleteRunnable.run(); 603 } 604 } 605 }); 606 va.setDuration(duration); 607 va.start(); 608 } 609 setTextVisible(boolean visible)610 public void setTextVisible(boolean visible) { 611 if (visible) { 612 mFolderName.setVisibility(VISIBLE); 613 } else { 614 mFolderName.setVisibility(INVISIBLE); 615 } 616 } 617 getTextVisible()618 public boolean getTextVisible() { 619 return mFolderName.getVisibility() == VISIBLE; 620 } 621 onItemsChanged()622 public void onItemsChanged() { 623 invalidate(); 624 requestLayout(); 625 } 626 onAdd(ShortcutInfo item)627 public void onAdd(ShortcutInfo item) { 628 invalidate(); 629 requestLayout(); 630 } 631 onRemove(ShortcutInfo item)632 public void onRemove(ShortcutInfo item) { 633 invalidate(); 634 requestLayout(); 635 } 636 onTitleChanged(CharSequence title)637 public void onTitleChanged(CharSequence title) { 638 mFolderName.setText(title.toString()); 639 setContentDescription(String.format(getContext().getString(R.string.folder_name_format), 640 title)); 641 } 642 643 @Override onTouchEvent(MotionEvent event)644 public boolean onTouchEvent(MotionEvent event) { 645 // Call the superclass onTouchEvent first, because sometimes it changes the state to 646 // isPressed() on an ACTION_UP 647 boolean result = super.onTouchEvent(event); 648 649 switch (event.getAction()) { 650 case MotionEvent.ACTION_DOWN: 651 mLongPressHelper.postCheckForLongPress(); 652 break; 653 case MotionEvent.ACTION_CANCEL: 654 case MotionEvent.ACTION_UP: 655 mLongPressHelper.cancelLongPress(); 656 break; 657 } 658 return result; 659 } 660 661 @Override cancelLongPress()662 public void cancelLongPress() { 663 super.cancelLongPress(); 664 665 mLongPressHelper.cancelLongPress(); 666 } 667 } 668