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