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