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