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