Cleaning up folder icon drawing
-> Tracking individual drawing parms for each item
-> This enables animation of the preview items as the preview changes
Change-Id: I1b8f650cb28dc09cfb921bbdc93f2a3db61178fd
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8c203f3..0476022 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -213,7 +213,7 @@
private boolean mIsSafeModeEnabled;
static final int APPWIDGET_HOST_ID = 1024;
- public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+ public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index d7f41c9..37200a6 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -26,7 +26,6 @@
// how long the user must hover over a mini-screen before it unshrinks
final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
- final long EXIT_SPRING_LOAD_HOVER_TIME = 200;
Alarm mAlarm;
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 44d7ac6..48988d7 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -42,22 +42,28 @@
public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
int curNumItems, FolderIcon.PreviewItemDrawingParams params) {
- getPosition(index, curNumItems, mTmpPoint);
- float transX = mTmpPoint[0];
- float transY = mTmpPoint[1];
float totalScale = scaleForNumItems(curNumItems);
+ float transX;
+ float transY;
float overlayAlpha = 0;
+ // Items beyond those displayed in the preview are animated to the center
+ if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
+ transX = transY = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
+ } else {
+ getPosition(index, curNumItems, mTmpPoint);
+ transX = mTmpPoint[0];
+ transY = mTmpPoint[1];
+ totalScale = scaleForNumItems(curNumItems);
+ }
+
if (params == null) {
params = new FolderIcon.PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
} else {
- params.transX = transX;
- params.transY = transY;
- params.scale = totalScale;
+ params.update(transX, transY, totalScale);
params.overlayAlpha = overlayAlpha;
}
-
return params;
}
@@ -65,7 +71,6 @@
// The case of two items is homomorphic to the case of one.
curNumItems = Math.max(curNumItems, 2);
-
// We model the preview as a circle of items starting in the appropriate piece of the
// upper left quadrant (to achieve horizontal and vertical symmetry).
double theta0 = mIsRtl ? 0 : Math.PI;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 5c084d9..7b71a36 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -134,9 +134,9 @@
private float mSlop;
- private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- @Thunk PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- @Thunk ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
+ private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+ private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
+ private Drawable mReferenceDrawable = null;
private Alarm mOpenAlarm = new Alarm();
@Thunk
@@ -158,7 +158,6 @@
mPreviewLayoutRule = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ?
new ClippedFolderIconLayoutRule() :
new StackFolderIconLayoutRule();
-
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
}
@@ -205,7 +204,7 @@
folder.setDragController(launcher.getDragController());
folder.setFolderIcon(icon);
folder.bind(folderInfo);
- icon.mFolder = folder;
+ icon.setFolder(folder);
icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
folderInfo.addListener(icon);
@@ -268,7 +267,7 @@
final int previewSize = sPreviewSize;
mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
- final float percent = (Float) animation.getAnimatedValue();
+ final float percent = animation.getAnimatedFraction();
mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize;
mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize;
if (mCellLayout != null) {
@@ -348,6 +347,11 @@
return mFolder;
}
+ private void setFolder(Folder folder) {
+ mFolder = folder;
+ updateItemDrawingParams(false);
+ }
+
public FolderInfo getFolderInfo() {
return mInfo;
}
@@ -414,10 +418,12 @@
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
+ mReferenceDrawable = animateDrawable;
+
+ addItem(destInfo);
// This will animate the first item from it's position as an icon into its
// position as the first item in the preview
animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
- addItem(destInfo);
// This will animate the dragView (srcView) into the new folder
onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
@@ -489,11 +495,14 @@
new DecelerateInterpolator(2), new AccelerateInterpolator(2),
postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
addItem(item);
- mHiddenItems.add(item);
mFolder.hideItem(item);
+
+ final PreviewItemDrawingParams params = index < mDrawingParams.size() ?
+ mDrawingParams.get(index) : null;
+ if (params != null) params.hidden = true;
postDelayed(new Runnable() {
public void run() {
- mHiddenItems.remove(item);
+ if (params != null) params.hidden = false;
mFolder.showItem(item);
invalidate();
}
@@ -532,6 +541,7 @@
mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize,
Utilities.isRtl(getResources()));
+ updateItemDrawingParams(false);
}
}
@@ -546,32 +556,66 @@
this.scale = scale;
this.overlayAlpha = overlayAlpha;
}
+
+ public void update(float transX, float transY, float scale) {
+ // We ensure the update will not interfere with an animation on the layout params
+ // If the final values differ, we cancel the animation.
+ if (anim != null) {
+ if (anim.finalTransX == transX || anim.finalTransY == transY
+ || anim.finalScale == scale) {
+ return;
+ }
+ anim.cancel();
+ }
+
+ this.transX = transX;
+ this.transY = transY;
+ this.scale = scale;
+ }
+
float transX;
float transY;
float scale;
- float overlayAlpha;
+ public float overlayAlpha;
+ boolean hidden;
+ FolderPreviewItemAnim anim;
Drawable drawable;
}
private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
- mParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
- curNumItems, mParams);
+ mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
+ curNumItems, mTmpParams);
- mParams.transX += mPreviewOffsetX;
- mParams.transY += mPreviewOffsetY;
- float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
- float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
+ mTmpParams.transX += mPreviewOffsetX;
+ mTmpParams.transY += mPreviewOffsetY;
+ float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2;
+ float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2;
center[0] = (int) Math.round(offsetX);
center[1] = (int) Math.round(offsetY);
- return mParams.scale;
+ return mTmpParams.scale;
}
private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
PreviewItemDrawingParams params) {
+ // We use an index of -1 to represent an icon on the workspace for the destroy and
+ // create animations
+ if (index == -1) {
+ return getFinalIconParams(params);
+ }
return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
}
+ private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
+ float iconSize = mLauncher.getDeviceProfile().iconSizePx;
+
+ final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
+ final float trans = (mAvailableSpaceInPreview - iconSize) / 2;
+
+ params.update(trans, trans, scale);
+ return params;
+ }
+
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
canvas.save();
canvas.translate(params.transX, params.transY);
@@ -605,17 +649,8 @@
if (mFolder == null) return;
if (mFolder.getItemCount() == 0 && !mAnimating) return;
- ArrayList<View> items = mFolder.getItemsInReadingOrder();
- Drawable d;
- TextView v;
-
- // Update our drawing parameters if necessary
- if (mAnimating) {
- computePreviewDrawingParams(mAnimParams.drawable);
- } else {
- v = (TextView) items.get(0);
- d = getTopDrawable(v);
- computePreviewDrawingParams(d);
+ if (mReferenceDrawable != null) {
+ computePreviewDrawingParams(mReferenceDrawable);
}
canvas.save();
@@ -625,19 +660,12 @@
canvas.clipPath(clipPath);
}
- int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
- if (!mAnimating) {
- for (int i = nItemsInPreview - 1; i >= 0; i--) {
- v = (TextView) items.get(i);
- if (!mHiddenItems.contains(v.getTag())) {
- d = getTopDrawable(v);
- mParams = computePreviewItemDrawingParams(i, nItemsInPreview, mParams);
- mParams.drawable = d;
- drawPreviewItem(canvas, mParams);
- }
+ // The first item should be drawn last (ie. on top of later items)
+ for (int i = mDrawingParams.size() - 1; i >= 0; i--) {
+ PreviewItemDrawingParams p = mDrawingParams.get(i);
+ if (!p.hidden) {
+ drawPreviewItem(canvas, p);
}
- } else {
- drawPreviewItem(canvas, mAnimParams);
}
canvas.restore();
}
@@ -647,48 +675,92 @@
return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
}
+ class FolderPreviewItemAnim {
+ ValueAnimator mValueAnimator;
+ float finalScale;
+ float finalTransX;
+ float finalTransY;
+
+ /**
+ *
+ * @param params layout params to animate
+ * @param index0 original index of the item to be animated
+ * @param nItems0 original number of items in the preview
+ * @param index1 new index of the item to be animated
+ * @param nItems1 new number of items in the preview
+ * @param duration duration in ms of the animation
+ * @param onCompleteRunnable runnable to execute upon animation completion
+ */
+ public FolderPreviewItemAnim(final PreviewItemDrawingParams params, int index0, int nItems0,
+ int index1, int nItems1, int duration, final Runnable onCompleteRunnable) {
+
+ computePreviewItemDrawingParams(index1, nItems1, mTmpParams);
+
+ finalScale = mTmpParams.scale;
+ finalTransX = mTmpParams.transX;
+ finalTransY = mTmpParams.transY;
+
+ computePreviewItemDrawingParams(index0, nItems0, mTmpParams);
+
+ final float scale0 = mTmpParams.scale;
+ final float transX0 = mTmpParams.transX;
+ final float transY0 = mTmpParams.transY;
+
+ mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
+ mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float progress = animation.getAnimatedFraction();
+
+ params.transX = transX0 + progress * (finalTransX - transX0);
+ params.transY = transY0 + progress * (finalTransY - transY0);
+ params.scale = scale0 + progress * (finalScale - scale0);
+ invalidate();
+ }
+ });
+
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ params.anim = null;
+ }
+ });
+ mValueAnimator.setDuration(duration);
+ }
+
+ public void start() {
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
+ return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
+ finalScale == anim.finalScale;
+
+ }
+ }
+
private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
final Runnable onCompleteRunnable) {
- final PreviewItemDrawingParams finalParams =
- computePreviewItemDrawingParams(0, reverse ? 1 : 2, null);
-
- float iconSize = mLauncher.getDeviceProfile().iconSizePx;
- final float scale0 = iconSize / d.getIntrinsicWidth() ;
- final float transX0 = (mAvailableSpaceInPreview - iconSize) / 2;
- final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2;
- mAnimParams.drawable = d;
-
- ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
- va.addUpdateListener(new AnimatorUpdateListener(){
- public void onAnimationUpdate(ValueAnimator animation) {
- float progress = (Float) animation.getAnimatedValue();
- if (reverse) {
- progress = 1 - progress;
- mPreviewBackground.setAlpha(progress);
- }
-
- mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0);
- mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0);
- mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0);
- invalidate();
- }
- });
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mAnimating = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimating = false;
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
- }
- });
- va.setDuration(duration);
- va.start();
+ FolderPreviewItemAnim anim;
+ if (!reverse) {
+ anim = new FolderPreviewItemAnim(mDrawingParams.get(0), -1, -1, 0, 2, duration,
+ onCompleteRunnable);
+ } else {
+ anim = new FolderPreviewItemAnim(mDrawingParams.get(0), 0, 2, -1, -1, duration,
+ onCompleteRunnable);
+ }
+ anim.start();
}
public void setTextVisible(boolean visible) {
@@ -703,7 +775,48 @@
return mFolderName.getVisibility() == VISIBLE;
}
+ private void updateItemDrawingParams(boolean animate) {
+ ArrayList<View> items = mFolder.getItemsInReadingOrder();
+ int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
+
+ int prevNumItems = mDrawingParams.size();
+
+ // We adjust the size of the list to match the number of items in the preview
+ while (nItemsInPreview < mDrawingParams.size()) {
+ mDrawingParams.remove(mDrawingParams.size() - 1);
+ }
+ while (nItemsInPreview > mDrawingParams.size()) {
+ mDrawingParams.add(new PreviewItemDrawingParams(0, 0, 0, 0));
+ }
+
+ for (int i = 0; i < mDrawingParams.size(); i++) {
+ PreviewItemDrawingParams p = mDrawingParams.get(i);
+ p.drawable = getTopDrawable((TextView) items.get(i));
+
+ if (!animate || !FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON) {
+ computePreviewItemDrawingParams(i, nItemsInPreview, p);
+ if (mReferenceDrawable == null) {
+ mReferenceDrawable = p.drawable;
+ }
+ } else {
+ FolderPreviewItemAnim anim = new FolderPreviewItemAnim(p, i, prevNumItems, i,
+ nItemsInPreview, DROP_IN_ANIMATION_DURATION, null);
+
+ if (p.anim != null) {
+ if (p.anim.hasEqualFinalState(anim)) {
+ // do nothing, let the current animation finish
+ continue;
+ }
+ p.anim.cancel();
+ }
+ p.anim = anim;
+ p.anim.start();
+ }
+ }
+ }
+
public void onItemsChanged() {
+ updateItemDrawingParams(true);
invalidate();
requestLayout();
}
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 87f5f89..0053072 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -73,9 +73,7 @@
if (params == null) {
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
} else {
- params.transX = transX;
- params.transY = transY;
- params.scale = totalScale;
+ params.update(transX, transY, totalScale);
params.overlayAlpha = overlayAlpha;
}
return params;