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