• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.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 
FolderIcon(Context context, AttributeSet attrs)115     public FolderIcon(Context context, AttributeSet attrs) {
116         super(context, attrs);
117         init();
118     }
119 
FolderIcon(Context context)120     public FolderIcon(Context context) {
121         super(context);
122         init();
123     }
124 
init()125     private void init() {
126         mLongPressHelper = new CheckLongPressHelper(this);
127     }
128 
isDropEnabled()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 
fromXml(int resId, Launcher launcher, ViewGroup group, FolderInfo folderInfo, IconCache iconCache)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
onSaveInstanceState()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 
FolderRingAnimator(Launcher launcher, FolderIcon folderIcon)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 
animateToAcceptState()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 
animateToNaturalState()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
getCell(int[] loc)287         public void getCell(int[] loc) {
288             loc[0] = mCellX;
289             loc[1] = mCellY;
290         }
291 
292         // Location is expressed in window coordinates
setCell(int x, int y)293         public void setCell(int x, int y) {
294             mCellX = x;
295             mCellY = y;
296         }
297 
setCellLayout(CellLayout layout)298         public void setCellLayout(CellLayout layout) {
299             mCellLayout = layout;
300         }
301 
getOuterRingSize()302         public float getOuterRingSize() {
303             return mOuterRingSize;
304         }
305 
getInnerRingSize()306         public float getInnerRingSize() {
307             return mInnerRingSize;
308         }
309     }
310 
getFolder()311     Folder getFolder() {
312         return mFolder;
313     }
314 
getFolderInfo()315     FolderInfo getFolderInfo() {
316         return mInfo;
317     }
318 
willAcceptItem(ItemInfo item)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 
acceptDrop(Object dragInfo)326     public boolean acceptDrop(Object dragInfo) {
327         final ItemInfo item = (ItemInfo) dragInfo;
328         return !mFolder.isDestroyed() && willAcceptItem(item);
329     }
330 
addItem(ShortcutInfo item)331     public void addItem(ShortcutInfo item) {
332         mInfo.add(item);
333     }
334 
onDragEnter(Object dragInfo)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 
onDragOver(Object dragInfo)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 
performCreateAnimation(final ShortcutInfo destInfo, final View destView, final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, float scaleRelativeToDragLayer, Runnable postAnimationRunnable)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 
performDestroyAnimation(final View finalView, Runnable onCompleteRunnable)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 
onDragExit(Object dragInfo)398     public void onDragExit(Object dragInfo) {
399         onDragExit();
400     }
401 
onDragExit()402     public void onDragExit() {
403         mFolderRingAnimator.animateToNaturalState();
404         mOpenAlarm.cancelAlarm();
405     }
406 
onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, DragObject d)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 
animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable)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 
setTextVisible(boolean visible)659     public void setTextVisible(boolean visible) {
660         if (visible) {
661             mFolderName.setVisibility(VISIBLE);
662         } else {
663             mFolderName.setVisibility(INVISIBLE);
664         }
665     }
666 
getTextVisible()667     public boolean getTextVisible() {
668         return mFolderName.getVisibility() == VISIBLE;
669     }
670 
onItemsChanged()671     public void onItemsChanged() {
672         invalidate();
673         requestLayout();
674     }
675 
onAdd(ShortcutInfo item)676     public void onAdd(ShortcutInfo item) {
677         invalidate();
678         requestLayout();
679     }
680 
onRemove(ShortcutInfo item)681     public void onRemove(ShortcutInfo item) {
682         invalidate();
683         requestLayout();
684     }
685 
onTitleChanged(CharSequence title)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
onTouchEvent(MotionEvent event)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
cancelLongPress()711     public void cancelLongPress() {
712         super.cancelLongPress();
713 
714         mLongPressHelper.cancelLongPress();
715     }
716 }
717