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