2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.folder;
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.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PorterDuff;
29 import android.graphics.Rect;
30 import android.graphics.Region;
31 import android.graphics.drawable.Drawable;
32 import android.os.Parcelable;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.ViewConfiguration;
39 import android.view.ViewGroup;
40 import android.view.animation.AccelerateInterpolator;
41 import android.view.animation.DecelerateInterpolator;
42 import android.widget.FrameLayout;
43 import android.widget.TextView;
45 import com.android.launcher3.Alarm;
46 import com.android.launcher3.AppInfo;
47 import com.android.launcher3.BubbleTextView;
48 import com.android.launcher3.CellLayout;
49 import com.android.launcher3.CheckLongPressHelper;
50 import com.android.launcher3.DeviceProfile;
51 import com.android.launcher3.DropTarget.DragObject;
52 import com.android.launcher3.FastBitmapDrawable;
53 import com.android.launcher3.FolderInfo;
54 import com.android.launcher3.FolderInfo.FolderListener;
55 import com.android.launcher3.IconCache;
56 import com.android.launcher3.ItemInfo;
57 import com.android.launcher3.Launcher;
58 import com.android.launcher3.LauncherAnimUtils;
59 import com.android.launcher3.LauncherAppState;
60 import com.android.launcher3.LauncherSettings;
61 import com.android.launcher3.OnAlarmListener;
62 import com.android.launcher3.PreloadIconDrawable;
63 import com.android.launcher3.R;
64 import com.android.launcher3.ShortcutInfo;
65 import com.android.launcher3.SimpleOnStylusPressListener;
66 import com.android.launcher3.StylusEventHelper;
67 import com.android.launcher3.Utilities;
68 import com.android.launcher3.Workspace;
69 import com.android.launcher3.config.FeatureFlags;
70 import com.android.launcher3.dragndrop.DragLayer;
71 import com.android.launcher3.dragndrop.DragView;
72 import com.android.launcher3.util.Thunk;
74 import java.util.ArrayList;
77 * An icon that can appear on in the workspace representing an {@link Folder}.
79 public class FolderIcon extends FrameLayout implements FolderListener {
80 @Thunk Launcher mLauncher;
81 @Thunk Folder mFolder;
82 private FolderInfo mInfo;
83 @Thunk static boolean sStaticValuesDirty = true;
85 public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
86 StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW :
87 ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
89 private CheckLongPressHelper mLongPressHelper;
90 private StylusEventHelper mStylusEventHelper;
92 // The number of icons to display in the
93 private static final int CONSUMPTION_ANIMATION_DURATION = 100;
94 private static final int DROP_IN_ANIMATION_DURATION = 400;
95 private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
96 private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
98 // Flag whether the folder should open itself when an item is dragged over is enabled.
99 public static final boolean SPRING_LOADING_ENABLED = true;
101 // Delay when drag enters until the folder opens, in miliseconds.
102 private static final int ON_OPEN_DELAY = 800;
104 @Thunk BubbleTextView mFolderName;
106 // These variables are all associated with the drawing of the preview; they are stored
107 // as member variables for shared usage and to avoid computation on each frame
108 private int mIntrinsicIconSize = -1;
109 private int mTotalWidth = -1;
110 private int mPrevTopPadding = -1;
112 PreviewBackground mBackground = new PreviewBackground();
114 private PreviewLayoutRule mPreviewLayoutRule;
116 boolean mAnimating = false;
117 private Rect mOldBounds = new Rect();
121 private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
122 private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
123 private Drawable mReferenceDrawable = null;
125 Paint mBgPaint = new Paint();
127 private Alarm mOpenAlarm = new Alarm();
129 public FolderIcon(Context context, AttributeSet attrs) {
130 super(context, attrs);
134 public FolderIcon(Context context) {
139 private void init() {
140 mLongPressHelper = new CheckLongPressHelper(this);
141 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
142 mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
143 new StackFolderIconLayoutRule() :
144 new ClippedFolderIconLayoutRule();
147 public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
148 FolderInfo folderInfo, IconCache iconCache) {
149 @SuppressWarnings("all") // suppress dead code warning
150 final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
152 throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
153 "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
154 "is dependent on this");
157 DeviceProfile grid = launcher.getDeviceProfile();
158 FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
160 // For performance and compatibility reasons we render the preview using a software layer.
161 // In particular, hardware path clipping has spotty ecosystem support and bad performance.
162 // Software rendering also allows us to use shadow layers.
163 icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
165 icon.setClipToPadding(false);
166 icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
167 icon.mFolderName.setText(folderInfo.title);
168 icon.mFolderName.setCompoundDrawablePadding(0);
169 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
170 lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
172 icon.setTag(folderInfo);
173 icon.setOnClickListener(launcher);
174 icon.mInfo = folderInfo;
175 icon.mLauncher = launcher;
176 icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
177 Folder folder = Folder.fromXml(launcher);
178 folder.setDragController(launcher.getDragController());
179 folder.setFolderIcon(icon);
180 folder.bind(folderInfo);
181 icon.setFolder(folder);
182 icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
184 folderInfo.addListener(icon);
186 icon.setOnFocusChangeListener(launcher.mFocusHandler);
191 protected Parcelable onSaveInstanceState() {
192 sStaticValuesDirty = true;
193 return super.onSaveInstanceState();
196 public Folder getFolder() {
200 private void setFolder(Folder folder) {
202 updateItemDrawingParams(false);
205 public FolderInfo getFolderInfo() {
209 private boolean willAcceptItem(ItemInfo item) {
210 final int itemType = item.itemType;
211 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
212 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
213 itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
214 !mFolder.isFull() && item != mInfo && !mInfo.opened);
217 public boolean acceptDrop(ItemInfo dragInfo) {
218 final ItemInfo item = dragInfo;
219 return !mFolder.isDestroyed() && willAcceptItem(item);
222 public void addItem(ShortcutInfo item) {
223 mInfo.add(item, true);
226 public void onDragEnter(ItemInfo dragInfo) {
227 if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
228 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
229 CellLayout cl = (CellLayout) getParent().getParent();
231 mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
232 mOpenAlarm.setOnAlarmListener(mOnOpenListener);
233 if (SPRING_LOADING_ENABLED &&
234 ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) {
235 // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even
236 // though widget-style shortcuts can be added to folders. The issue is that we need
237 // to deal with configuration activities which are currently handled in
238 // Workspace#onDropExternal.
239 mOpenAlarm.setAlarm(ON_OPEN_DELAY);
243 OnAlarmListener mOnOpenListener = new OnAlarmListener() {
244 public void onAlarm(Alarm alarm) {
245 mFolder.beginExternalDrag();
246 mLauncher.openFolder(FolderIcon.this);
250 public Drawable prepareCreate(final View destView) {
251 Drawable animateDrawable = getTopDrawable((TextView) destView);
252 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
253 destView.getMeasuredWidth());
254 return animateDrawable;
257 public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
258 final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
259 float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
261 // These correspond two the drawable and view that the icon was dropped _onto_
262 Drawable animateDrawable = prepareCreate(destView);
264 mReferenceDrawable = animateDrawable;
267 // This will animate the first item from it's position as an icon into its
268 // position as the first item in the preview
269 animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
271 // This will animate the dragView (srcView) into the new folder
272 onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
275 public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
276 Drawable animateDrawable = getTopDrawable((TextView) finalView);
277 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
278 finalView.getMeasuredWidth());
280 // This will animate the first item from it's position as an icon into its
281 // position as the first item in the preview
282 animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
286 public void onDragExit() {
287 mBackground.animateToRest();
288 mOpenAlarm.cancelAlarm();
291 private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
292 float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
296 // Typically, the animateView corresponds to the DragView; however, if this is being done
297 // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
298 // will not have a view to animate
299 if (animateView != null) {
300 DragLayer dragLayer = mLauncher.getDragLayer();
301 Rect from = new Rect();
302 dragLayer.getViewRectRelativeToSelf(animateView, from);
306 Workspace workspace = mLauncher.getWorkspace();
307 // Set cellLayout and this to it's final state to compute final animation locations
308 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
309 float scaleX = getScaleX();
310 float scaleY = getScaleY();
313 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
314 // Finished computing final animation locations, restore current state
317 workspace.resetTransitionTransform((CellLayout) getParent().getParent());
320 int[] center = new int[2];
321 float scale = getLocalCenterForIndex(index, index + 1, center);
322 center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
323 center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
325 to.offset(center[0] - animateView.getMeasuredWidth() / 2,
326 center[1] - animateView.getMeasuredHeight() / 2);
328 float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
330 float finalScale = scale * scaleRelativeToDragLayer;
331 dragLayer.animateView(animateView, from, to, finalAlpha,
332 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
333 new DecelerateInterpolator(2), new AccelerateInterpolator(2),
334 postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
336 mFolder.hideItem(item);
338 final PreviewItemDrawingParams params = index < mDrawingParams.size() ?
339 mDrawingParams.get(index) : null;
340 if (params != null) params.hidden = true;
341 postDelayed(new Runnable() {
343 if (params != null) params.hidden = false;
344 mFolder.showItem(item);
347 }, DROP_IN_ANIMATION_DURATION);
353 public void onDrop(DragObject d) {
355 if (d.dragInfo instanceof AppInfo) {
356 // Came from all apps -- make a copy
357 item = ((AppInfo) d.dragInfo).makeShortcut();
359 item = (ShortcutInfo) d.dragInfo;
361 mFolder.notifyDrop();
362 onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
365 private void computePreviewDrawingParams(int drawableSize, int totalSize) {
366 if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
367 mPrevTopPadding != getPaddingTop()) {
368 DeviceProfile grid = mLauncher.getDeviceProfile();
370 mIntrinsicIconSize = drawableSize;
371 mTotalWidth = totalSize;
372 mPrevTopPadding = getPaddingTop();
374 mBackground.setup(getResources().getDisplayMetrics(), grid, this, mTotalWidth,
376 mPreviewLayoutRule.init(mBackground.previewSize, mIntrinsicIconSize,
377 Utilities.isRtl(getResources()));
379 updateItemDrawingParams(false);
383 private void computePreviewDrawingParams(Drawable d) {
384 computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
387 static class PreviewItemDrawingParams {
388 PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
389 this.transX = transX;
390 this.transY = transY;
392 this.overlayAlpha = overlayAlpha;
395 public void update(float transX, float transY, float scale) {
396 // We ensure the update will not interfere with an animation on the layout params
397 // If the final values differ, we cancel the animation.
399 if (anim.finalTransX == transX || anim.finalTransY == transY
400 || anim.finalScale == scale) {
406 this.transX = transX;
407 this.transY = transY;
414 public float overlayAlpha;
416 FolderPreviewItemAnim anim;
420 private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
421 mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
422 curNumItems, mTmpParams);
424 mTmpParams.transX += mBackground.basePreviewOffsetX;
425 mTmpParams.transY += mBackground.basePreviewOffsetY;
426 float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2;
427 float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2;
429 center[0] = (int) Math.round(offsetX);
430 center[1] = (int) Math.round(offsetY);
431 return mTmpParams.scale;
434 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
435 PreviewItemDrawingParams params) {
436 // We use an index of -1 to represent an icon on the workspace for the destroy and
439 return getFinalIconParams(params);
441 return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
444 private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
445 float iconSize = mLauncher.getDeviceProfile().iconSizePx;
447 final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
448 final float trans = (mBackground.previewSize - iconSize) / 2;
450 params.update(trans, trans, scale);
454 private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
456 canvas.translate(params.transX, params.transY);
457 canvas.scale(params.scale, params.scale);
458 Drawable d = params.drawable;
461 mOldBounds.set(d.getBounds());
462 d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
463 if (d instanceof FastBitmapDrawable) {
464 FastBitmapDrawable fd = (FastBitmapDrawable) d;
465 float oldBrightness = fd.getBrightness();
466 fd.setBrightness(params.overlayAlpha);
468 fd.setBrightness(oldBrightness);
470 d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
471 PorterDuff.Mode.SRC_ATOP);
473 d.clearColorFilter();
475 d.setBounds(mOldBounds);
481 * This object represents a FolderIcon preview background. It stores drawing / measurement
482 * information, handles drawing, and animation (accept state <--> rest state).
484 public static class PreviewBackground {
485 private float mScale = 1f;
486 private float mColorMultiplier = 1f;
487 private Path mClipPath = new Path();
488 private int mStrokeWidth;
489 private View mInvalidateDelegate;
491 public int previewSize;
492 private int basePreviewOffsetX;
493 private int basePreviewOffsetY;
495 private CellLayout mDrawingDelegate;
496 public int delegateCellX;
497 public int delegateCellY;
499 // When the PreviewBackground is drawn under an icon (for creating a folder) the border
500 // should not occlude the icon
501 public boolean isClipping = true;
503 // Drawing / animation configurations
504 private static final float ACCEPT_SCALE_FACTOR = 1.25f;
505 private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
507 // Expressed on a scale from 0 to 255.
508 private static final int BG_OPACITY = 160;
509 private static final int MAX_BG_OPACITY = 225;
510 private static final int BG_INTENSITY = 245;
511 private static final int SHADOW_OPACITY = 80;
513 ValueAnimator mScaleAnimator;
515 public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDelegate,
516 int availableSpace, int topPadding) {
517 mInvalidateDelegate = invalidateDelegate;
519 final int previewSize = grid.folderIconSizePx;
520 final int previewPadding = grid.folderIconPreviewPadding;
522 this.previewSize = (previewSize - 2 * previewPadding);
524 basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
525 basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
527 mStrokeWidth = Utilities.pxFromDp(1, dm);
533 return previewSize / 2;
536 int getScaledRadius() {
537 return (int) (mScale * getRadius());
541 return basePreviewOffsetX - (getScaledRadius() - getRadius());
545 return basePreviewOffsetY - (getScaledRadius() - getRadius());
549 int radius = getScaledRadius();
551 mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
553 if (mInvalidateDelegate != null) {
554 mInvalidateDelegate.invalidate();
557 if (mDrawingDelegate != null) {
558 mDrawingDelegate.invalidate();
562 void setInvalidateDelegate(View invalidateDelegate) {
563 mInvalidateDelegate = invalidateDelegate;
567 public void drawBackground(Canvas canvas, Paint paint) {
569 canvas.translate(getOffsetX(), getOffsetY());
572 paint.setStyle(Paint.Style.FILL);
573 paint.setXfermode(null);
574 paint.setAntiAlias(true);
576 int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
577 paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
579 float radius = getScaledRadius();
581 canvas.drawCircle(radius, radius, radius, paint);
582 canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
584 paint.setStyle(Paint.Style.STROKE);
585 paint.setColor(Color.TRANSPARENT);
586 paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
587 canvas.drawCircle(radius, radius, radius, paint);
592 public void drawBackgroundStroke(Canvas canvas, Paint paint) {
594 canvas.translate(getOffsetX(), getOffsetY());
597 paint.setAntiAlias(true);
598 paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
599 paint.setStyle(Paint.Style.STROKE);
600 paint.setStrokeWidth(mStrokeWidth);
602 float radius = getScaledRadius();
603 canvas.drawCircle(radius, radius, radius - 1, paint);
608 public void drawLeaveBehind(Canvas canvas, Paint paint) {
609 float originalScale = mScale;
613 canvas.translate(getOffsetX(), getOffsetY());
616 paint.setAntiAlias(true);
617 paint.setColor(Color.argb(160, 245, 245, 245));
619 float radius = getScaledRadius();
620 canvas.drawCircle(radius, radius, radius, paint);
623 mScale = originalScale;
626 // It is the callers responsibility to save and restore the canvas.
627 private void clipCanvas(Canvas canvas) {
628 canvas.translate(getOffsetX(), getOffsetY());
629 canvas.clipPath(mClipPath);
630 canvas.translate(-getOffsetX(), -getOffsetY());
633 private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
634 if (mDrawingDelegate != delegate) {
635 delegate.addFolderBackground(this);
638 mDrawingDelegate = delegate;
639 delegateCellX = cellX;
640 delegateCellY = cellY;
645 private void clearDrawingDelegate() {
646 if (mDrawingDelegate != null) {
647 mDrawingDelegate.removeFolderBackground(this);
650 mDrawingDelegate = null;
654 private boolean drawingDelegated() {
655 return mDrawingDelegate != null;
658 private void animateScale(float finalScale, float finalMultiplier,
659 final Runnable onStart, final Runnable onEnd) {
660 final float scale0 = mScale;
661 final float scale1 = finalScale;
663 final float bgMultiplier0 = mColorMultiplier;
664 final float bgMultiplier1 = finalMultiplier;
666 if (mScaleAnimator != null) {
667 mScaleAnimator.cancel();
670 mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f);
672 mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
674 public void onAnimationUpdate(ValueAnimator animation) {
675 float prog = animation.getAnimatedFraction();
676 mScale = prog * scale1 + (1 - prog) * scale0;
677 mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
681 mScaleAnimator.addListener(new AnimatorListenerAdapter() {
683 public void onAnimationStart(Animator animation) {
684 if (onStart != null) {
690 public void onAnimationEnd(Animator animation) {
694 mScaleAnimator = null;
698 mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
699 mScaleAnimator.start();
702 public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
703 Runnable onStart = new Runnable() {
706 delegateDrawing(cl, cellX, cellY);
709 animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
712 public void animateToRest() {
713 // This can be called multiple times -- we need to make sure the drawing delegate
714 // is saved and restored at the beginning of the animation, since cancelling the
715 // existing animation can clear the delgate.
716 final CellLayout cl = mDrawingDelegate;
717 final int cellX = delegateCellX;
718 final int cellY = delegateCellY;
720 Runnable onStart = new Runnable() {
723 delegateDrawing(cl, cellX, cellY);
726 Runnable onEnd = new Runnable() {
729 clearDrawingDelegate();
732 animateScale(1f, 1f, onStart, onEnd);
736 public void setFolderBackground(PreviewBackground bg) {
738 mBackground.setInvalidateDelegate(this);
742 protected void dispatchDraw(Canvas canvas) {
743 super.dispatchDraw(canvas);
745 if (mReferenceDrawable != null) {
746 computePreviewDrawingParams(mReferenceDrawable);
749 if (!mBackground.drawingDelegated()) {
750 mBackground.drawBackground(canvas, mBgPaint);
753 if (mFolder == null) return;
754 if (mFolder.getItemCount() == 0 && !mAnimating) return;
759 if (mPreviewLayoutRule.clipToBackground()) {
760 mBackground.clipCanvas(canvas);
763 // The items are drawn in coordinates relative to the preview offset
764 canvas.translate(mBackground.basePreviewOffsetX, mBackground.basePreviewOffsetY);
766 // The first item should be drawn last (ie. on top of later items)
767 for (int i = mDrawingParams.size() - 1; i >= 0; i--) {
768 PreviewItemDrawingParams p = mDrawingParams.get(i);
770 drawPreviewItem(canvas, p);
775 if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
776 mBackground.drawBackgroundStroke(canvas, mBgPaint);
780 private Drawable getTopDrawable(TextView v) {
781 Drawable d = v.getCompoundDrawables()[1];
782 return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
785 class FolderPreviewItemAnim {
786 ValueAnimator mValueAnimator;
793 * @param params layout params to animate
794 * @param index0 original index of the item to be animated
795 * @param nItems0 original number of items in the preview
796 * @param index1 new index of the item to be animated
797 * @param nItems1 new number of items in the preview
798 * @param duration duration in ms of the animation
799 * @param onCompleteRunnable runnable to execute upon animation completion
801 public FolderPreviewItemAnim(final PreviewItemDrawingParams params, int index0, int nItems0,
802 int index1, int nItems1, int duration, final Runnable onCompleteRunnable) {
804 computePreviewItemDrawingParams(index1, nItems1, mTmpParams);
806 finalScale = mTmpParams.scale;
807 finalTransX = mTmpParams.transX;
808 finalTransY = mTmpParams.transY;
810 computePreviewItemDrawingParams(index0, nItems0, mTmpParams);
812 final float scale0 = mTmpParams.scale;
813 final float transX0 = mTmpParams.transX;
814 final float transY0 = mTmpParams.transY;
816 mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
817 mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
818 public void onAnimationUpdate(ValueAnimator animation) {
819 float progress = animation.getAnimatedFraction();
821 params.transX = transX0 + progress * (finalTransX - transX0);
822 params.transY = transY0 + progress * (finalTransY - transY0);
823 params.scale = scale0 + progress * (finalScale - scale0);
828 mValueAnimator.addListener(new AnimatorListenerAdapter() {
830 public void onAnimationStart(Animator animation) {
834 public void onAnimationEnd(Animator animation) {
835 if (onCompleteRunnable != null) {
836 onCompleteRunnable.run();
841 mValueAnimator.setDuration(duration);
844 public void start() {
845 mValueAnimator.start();
848 public void cancel() {
849 mValueAnimator.cancel();
852 public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
853 return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
854 finalScale == anim.finalScale;
859 private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
860 final Runnable onCompleteRunnable) {
862 FolderPreviewItemAnim anim;
864 anim = new FolderPreviewItemAnim(mDrawingParams.get(0), -1, -1, 0, 2, duration,
867 anim = new FolderPreviewItemAnim(mDrawingParams.get(0), 0, 2, -1, -1, duration,
873 public void setTextVisible(boolean visible) {
875 mFolderName.setVisibility(VISIBLE);
877 mFolderName.setVisibility(INVISIBLE);
881 public boolean getTextVisible() {
882 return mFolderName.getVisibility() == VISIBLE;
885 private void updateItemDrawingParams(boolean animate) {
886 ArrayList<View> items = mFolder.getItemsInReadingOrder();
887 int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
889 int prevNumItems = mDrawingParams.size();
891 // We adjust the size of the list to match the number of items in the preview
892 while (nItemsInPreview < mDrawingParams.size()) {
893 mDrawingParams.remove(mDrawingParams.size() - 1);
895 while (nItemsInPreview > mDrawingParams.size()) {
896 mDrawingParams.add(new PreviewItemDrawingParams(0, 0, 0, 0));
899 for (int i = 0; i < mDrawingParams.size(); i++) {
900 PreviewItemDrawingParams p = mDrawingParams.get(i);
901 p.drawable = getTopDrawable((TextView) items.get(i));
903 if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
904 computePreviewItemDrawingParams(i, nItemsInPreview, p);
905 if (mReferenceDrawable == null) {
906 mReferenceDrawable = p.drawable;
909 FolderPreviewItemAnim anim = new FolderPreviewItemAnim(p, i, prevNumItems, i,
910 nItemsInPreview, DROP_IN_ANIMATION_DURATION, null);
912 if (p.anim != null) {
913 if (p.anim.hasEqualFinalState(anim)) {
914 // do nothing, let the current animation finish
926 public void onItemsChanged(boolean animate) {
927 updateItemDrawingParams(animate);
932 public void onAdd(ShortcutInfo item) {
937 public void onRemove(ShortcutInfo item) {
942 public void onTitleChanged(CharSequence title) {
943 mFolderName.setText(title);
944 setContentDescription(getContext().getString(R.string.folder_name_format, title));
948 public boolean onTouchEvent(MotionEvent event) {
949 // Call the superclass onTouchEvent first, because sometimes it changes the state to
950 // isPressed() on an ACTION_UP
951 boolean result = super.onTouchEvent(event);
953 // Check for a stylus button press, if it occurs cancel any long press checks.
954 if (mStylusEventHelper.onMotionEvent(event)) {
955 mLongPressHelper.cancelLongPress();
959 switch (event.getAction()) {
960 case MotionEvent.ACTION_DOWN:
961 mLongPressHelper.postCheckForLongPress();
963 case MotionEvent.ACTION_CANCEL:
964 case MotionEvent.ACTION_UP:
965 mLongPressHelper.cancelLongPress();
967 case MotionEvent.ACTION_MOVE:
968 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
969 mLongPressHelper.cancelLongPress();
977 protected void onAttachedToWindow() {
978 super.onAttachedToWindow();
979 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
983 public void cancelLongPress() {
984 super.cancelLongPress();
985 mLongPressHelper.cancelLongPress();
988 public void removeListeners() {
989 mInfo.removeListener(this);
990 mInfo.removeListener(mFolder);
993 public interface PreviewLayoutRule {
994 public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
995 PreviewItemDrawingParams params);
997 public void init(int availableSpace, int intrinsicIconSize, boolean rtl);
999 public int numItems();
1000 public boolean clipToBackground();