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.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 
    102     public FolderIcon(Context context, AttributeSet attrs) {
    103         super(context, attrs);
    104         init();
    105     }
    106 
    107     public FolderIcon(Context context) {
    108         super(context);
    109         init();
    110     }
    111 
    112     private void init() {
    113         mLongPressHelper = new CheckLongPressHelper(this);
    114     }
    115 
    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 
    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
    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 
    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 
    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 
    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
    260         public void getCell(int[] loc) {
    261             loc[0] = mCellX;
    262             loc[1] = mCellY;
    263         }
    264 
    265         // Location is expressed in window coordinates
    266         public void setCell(int x, int y) {
    267             mCellX = x;
    268             mCellY = y;
    269         }
    270 
    271         public void setCellLayout(CellLayout layout) {
    272             mCellLayout = layout;
    273         }
    274 
    275         public float getOuterRingSize() {
    276             return mOuterRingSize;
    277         }
    278 
    279         public float getInnerRingSize() {
    280             return mInnerRingSize;
    281         }
    282     }
    283 
    284     Folder getFolder() {
    285         return mFolder;
    286     }
    287 
    288     FolderInfo getFolderInfo() {
    289         return mInfo;
    290     }
    291 
    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 
    299     public boolean acceptDrop(Object dragInfo) {
    300         final ItemInfo item = (ItemInfo) dragInfo;
    301         return !mFolder.isDestroyed() && willAcceptItem(item);
    302     }
    303 
    304     public void addItem(ShortcutInfo item) {
    305         mInfo.add(item);
    306     }
    307 
    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 
    318     public void onDragOver(Object dragInfo) {
    319     }
    320 
    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 
    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 
    350     public void onDragExit(Object dragInfo) {
    351         onDragExit();
    352     }
    353 
    354     public void onDragExit() {
    355         mFolderRingAnimator.animateToNaturalState();
    356     }
    357 
    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 
    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 
    610     public void setTextVisible(boolean visible) {
    611         if (visible) {
    612             mFolderName.setVisibility(VISIBLE);
    613         } else {
    614             mFolderName.setVisibility(INVISIBLE);
    615         }
    616     }
    617 
    618     public boolean getTextVisible() {
    619         return mFolderName.getVisibility() == VISIBLE;
    620     }
    621 
    622     public void onItemsChanged() {
    623         invalidate();
    624         requestLayout();
    625     }
    626 
    627     public void onAdd(ShortcutInfo item) {
    628         invalidate();
    629         requestLayout();
    630     }
    631 
    632     public void onRemove(ShortcutInfo item) {
    633         invalidate();
    634         requestLayout();
    635     }
    636 
    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
    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
    662     public void cancelLongPress() {
    663         super.cancelLongPress();
    664 
    665         mLongPressHelper.cancelLongPress();
    666     }
    667 }
    668