• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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