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