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