Merge "Folder animation polish." into ub-launcher3-dorval-polish
diff --git a/res/values/config.xml b/res/values/config.xml
index db1a75d..00db4ef 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -124,7 +124,7 @@
<item type="id" name="preview_image_id" />
<!-- Popup items -->
- <integer name="config_popupOpenCloseDuration">220</integer>
+ <integer name="config_popupOpenCloseDuration">150</integer>
<integer name="config_popupArrowOpenDuration">80</integer>
<integer name="config_removeNotificationViewDuration">300</integer>
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 21254ab..7959d40 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -45,6 +45,12 @@
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+ /**
+ * The folder items ranks have been updated such that they appear unchanged with the new
+ * permutation display logic.
+ */
+ public static final int FLAG_ITEM_RANKS_UPDATED = 0x00000008;
+
public int options;
/**
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 4813571..a4cd191 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,6 +56,7 @@
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractionUtils;
+import com.android.launcher3.folder.FolderPagedView;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
@@ -725,6 +726,87 @@
+ "';l.profile=" + serial + ";', ';') where itemType = 0;";
db.execSQL(sql);
}
+
+ updateExistingFoldersToMatchPrePermutationLayout(db);
+ }
+
+ /**
+ * We have changed the way we display items in Folders, but we want existing folders to
+ * appear the same.
+ *
+ * To make this change invisible to existing Folders, we need to update the ranks of the
+ * items such that, when displayed using the permutation, the order remains the same.
+ */
+ private void updateExistingFoldersToMatchPrePermutationLayout(SQLiteDatabase db) {
+ InvariantDeviceProfile idp = new InvariantDeviceProfile(mContext);
+ int maxCols = idp.numFolderColumns;
+ int maxRows = idp.numFolderRows;
+
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ Cursor c = db.query(Favorites.TABLE_NAME,
+ new String[] {Favorites._ID, Favorites.OPTIONS},
+ "itemType=" + Favorites.ITEM_TYPE_FOLDER, null, null, null, null);
+
+ // For every Folder
+ while (c.moveToNext()) {
+ final long folderId = c.getLong(c.getColumnIndexOrThrow(Favorites._ID));
+ int options = c.getInt(c.getColumnIndexOrThrow(Favorites.OPTIONS));
+
+ if ((options & FolderInfo.FLAG_ITEM_RANKS_UPDATED) != 0) {
+ // The folder has already been updated.
+ continue;
+ }
+
+ // For each item in the Folder
+ Cursor c2 = db.query(Favorites.TABLE_NAME, new String[] {
+ Favorites._ID, Favorites.RANK, Favorites.CELLX, Favorites.CELLY,
+ Favorites.OPTIONS},
+ "container=" + folderId, null, null, null, null);
+
+ int numItemsInFolder = c2.getCount();
+
+ // Calculate the grid size.
+ int[] gridXY = new int[2];
+ FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, maxCols, maxRows,
+ maxCols * maxRows, gridXY);
+ int gridX = gridXY[0];
+ int gridY = gridXY[1];
+ int maxItemsPerPage = gridX * gridY;
+
+ // We create a mapping from the permutation to the original rank (ie. the
+ // inverse permutation). This is what we'll use to set the folder items so that
+ // they appear in their original order.
+ int[] inversion = new int[numItemsInFolder];
+ for (int i = 0; i < numItemsInFolder; ++i) {
+ int permutation = FolderPagedView.getReadingOrderPosForRank(i,
+ maxItemsPerPage, gridX, null);
+ inversion[permutation] = i;
+ }
+
+ // Now we update the ranks of the folder items. Note that cellX/cellY stay the
+ // same, due to the permutation.
+ for (int i = 0; i < numItemsInFolder && c2.moveToNext(); ++i) {
+ final int rank = c2.getInt(c2.getColumnIndexOrThrow(Favorites.RANK));
+
+ SQLiteStatement updateItem = db.compileStatement(
+ "UPDATE favorites SET rank=" + inversion[rank] + " WHERE _id=?");
+ updateItem.bindLong(1, c2.getInt(c2.getColumnIndexOrThrow(Favorites._ID)));
+ updateItem.executeUpdateDelete();
+ }
+ c2.close();
+
+ // Mark the folder as having been updated.
+ options |= FolderInfo.FLAG_ITEM_RANKS_UPDATED;
+ SQLiteStatement updateFolder = db.compileStatement(
+ "UPDATE favorites SET options=" + options + " WHERE _id=?");
+ updateFolder.bindLong(1, folderId);
+ updateFolder.executeUpdateDelete();
+ }
+ c.close();
+ t.commit();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Error updating folder items to match permutation.", ex);
+ }
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 189b935..499eb45 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -423,8 +423,9 @@
// We only show the spring animation when at the top or bottom, so we wait until the
// first or last row is visible to ensure that all animations run in sync.
- if ((first == 0 && dy < 0) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
- mSpringAnimationHandler.animateToFinalPosition(0);
+ boolean scrollUp = dy < 0;
+ if ((first == 0 && scrollUp) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
+ mSpringAnimationHandler.animateToFinalPosition(0, scrollUp ? 1 : -1);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a2bd43d..34421bd 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -511,7 +511,9 @@
if (FeatureFlags.LAUNCHER3_PHYSICS) {
// We calculate our own velocity to give the springs the desired effect.
velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
- mSpringAnimationHandler.animateToPositionWithVelocity(0, -velocity);
+ // We want to negate the velocity because we are moving to 0 from -1 due to the
+ // downward motion. (y-axis -1 is above 0).
+ mSpringAnimationHandler.animateToPositionWithVelocity(0, -1, -velocity);
}
ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4d112c6..0859e06 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -228,7 +228,8 @@
}
mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */);
if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.animateToFinalPosition(0);
+ // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+ mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
}
} else {
calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index 1efc4e4..3e58adc 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -111,16 +111,32 @@
mShouldComputeVelocity = true;
}
- public void animateToFinalPosition(float position) {
- if (DEBUG) Log.d(TAG, "animateToFinalPosition#computeVelocity=" + mShouldComputeVelocity);
+ public void animateToFinalPosition(float position, int startValue) {
+ animateToFinalPosition(position, startValue, mShouldComputeVelocity);
+ }
+
+ /**
+ * @param position The final animation position.
+ * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start
+ * The magnitude of the number changes how the spring will move.
+ * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before
+ * starting the animation.
+ */
+ private void animateToFinalPosition(float position, int startValue, boolean setVelocity) {
+ if (DEBUG) {
+ Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue);
+ }
if (mShouldComputeVelocity) {
- computeVelocity();
- setStartVelocity(mCurrentVelocity);
+ mCurrentVelocity = computeVelocity();
}
int size = mAnimations.size();
for (int i = 0; i < size; ++i) {
+ mAnimations.get(i).setStartValue(startValue);
+ if (setVelocity) {
+ mAnimations.get(i).setStartVelocity(mCurrentVelocity);
+ }
mAnimations.get(i).animateToFinalPosition(position);
}
@@ -128,15 +144,18 @@
}
/**
- * Similar to {@link #animateToFinalPosition(float)}, but used in cases where we want to
+ * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to
* manually set the velocity.
*/
- public void animateToPositionWithVelocity(float position, float velocity) {
- if (DEBUG) Log.d(TAG, "animateToPosition#velocity=" + velocity);
+ public void animateToPositionWithVelocity(float position, int startValue, float velocity) {
+ if (DEBUG) {
+ Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue
+ + ", velocity=" + velocity);
+ }
- setStartVelocity(velocity);
+ mCurrentVelocity = velocity;
mShouldComputeVelocity = false;
- animateToFinalPosition(position);
+ animateToFinalPosition(position, startValue, true);
}
@@ -163,27 +182,20 @@
mVelocityTracker = null;
}
mCurrentVelocity = 0;
+ mShouldComputeVelocity = false;
}
- private void setStartVelocity(float velocity) {
- if (DEBUG) Log.d(TAG, "setStartVelocity=" + velocity);
- int size = mAnimations.size();
- for (int i = 0; i < size; ++i) {
- mAnimations.get(i).setStartVelocity(velocity);
- }
- }
-
- private void computeVelocity() {
+ private float computeVelocity() {
getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
- mCurrentVelocity = isVerticalDirection()
+ float velocity = isVerticalDirection()
? getVelocityTracker().getYVelocity()
: getVelocityTracker().getXVelocity();
- mCurrentVelocity *= VELOCITY_DAMPING_FACTOR;
- mShouldComputeVelocity = false;
+ velocity *= VELOCITY_DAMPING_FACTOR;
- if (DEBUG) Log.d(TAG, "computeVelocity=" + mCurrentVelocity);
+ if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity);
+ return velocity;
}
private boolean isVerticalDirection() {
@@ -221,7 +233,6 @@
*/
public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
SpringAnimation spring = new SpringAnimation(view, property, finalPos);
- spring.setStartValue(1f);
spring.setSpring(new SpringForce(finalPos));
return spring;
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 21631fa..bc26fbe 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -755,6 +755,10 @@
return mMaxItemsPerPage;
}
+ public int getReadingOrderPosForRank(int rank) {
+ return getReadingOrderPosForRank(rank, mMaxItemsPerPage, mGridCountX, sTmpArray);
+ }
+
/**
* Returns the reading order position for a given rank.
*
@@ -763,34 +767,44 @@
*
* R0 R1 R4
* R2 R3 R5
+ *
+ * @param outXY If notnull, we also return the cell X/Y position.
*/
- public int getReadingOrderPosForRank(int rank) {
- if (rank >= mMaxItemsPerPage) {
+ public static int getReadingOrderPosForRank(int rank, int maxItemsPerPage, int gridX,
+ int[] outXY) {
+ outXY = outXY == null ? sTmpArray : outXY;
+ getCellXYPositionForRank(rank, maxItemsPerPage, gridX, outXY);
+
+ if (rank >= maxItemsPerPage) {
return rank;
}
- getCellXYPositionForRank(rank, sTmpArray);
- return sTmpArray[0] + (mGridCountX * sTmpArray[1]);
+ return outXY[0] + (gridX * outXY[1]);
+ }
+
+ public void getCellXYPositionForRank(int rank, int[] outXY) {
+ getCellXYPositionForRank(rank, mMaxItemsPerPage, mGridCountX, outXY);
}
/**
* Returns the cell XY position for a Folder item with the given rank.
*/
- public void getCellXYPositionForRank(int rank, int[] outXY) {
- boolean onFirstPage = rank < mMaxItemsPerPage;
+ public static void getCellXYPositionForRank(int rank, int maxItemsPerPage, int gridX,
+ int[] outXY) {
+ boolean onFirstPage = rank < maxItemsPerPage;
- if (onFirstPage && mGridCountX == 3) {
+ if (onFirstPage && gridX == 3) {
outXY[0] = FolderPermutation.THREE_COLS[rank][0];
outXY[1] = FolderPermutation.THREE_COLS[rank][1];
- } else if (onFirstPage && mGridCountX == 4) {
+ } else if (onFirstPage && gridX == 4) {
outXY[0] = FolderPermutation.FOUR_COLS[rank][0];
outXY[1] = FolderPermutation.FOUR_COLS[rank][1];
- } else if (onFirstPage && mGridCountX == 5) {
+ } else if (onFirstPage && gridX == 5) {
outXY[0] = FolderPermutation.FIVE_COLS[rank][0];
outXY[1] = FolderPermutation.FIVE_COLS[rank][1];
} else {
- outXY[0] = (rank % mMaxItemsPerPage) % mGridCountX;
- outXY[1] = (rank % mMaxItemsPerPage) / mGridCountX;
+ outXY[0] = (rank % maxItemsPerPage) % gridX;
+ outXY[1] = (rank % maxItemsPerPage) / gridX;
}
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8107625..c6ae0d2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
@@ -353,6 +354,8 @@
final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
+ final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
// Rectangular reveal.
int itemsTotalHeight = 0;
@@ -366,8 +369,13 @@
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
(radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
- revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
- revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+ revealAnim.setDuration(revealDuration);
+ revealAnim.setInterpolator(revealInterpolator);
+
+ Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+ fadeIn.setDuration(revealDuration);
+ fadeIn.setInterpolator(revealInterpolator);
+ openAnim.play(fadeIn);
// Animate the arrow.
mArrow.setScaleX(0);
@@ -851,10 +859,8 @@
final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
-
- // Animate the arrow.
- Animator arrowScale = createArrowScaleAnim(0).setDuration(res.getInteger(
- R.integer.config_popupArrowOpenDuration));
+ final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
// Rectangular reveal (reversed).
int itemsTotalHeight = 0;
@@ -870,9 +876,14 @@
}
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
- long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
revealAnim.setDuration(revealDuration);
- revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+ revealAnim.setInterpolator(revealInterpolator);
+ closeAnim.play(revealAnim);
+
+ Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+ fadeOut.setDuration(revealDuration);
+ fadeOut.setInterpolator(revealInterpolator);
+ closeAnim.play(fadeOut);
// Animate original icon's text back in.
Animator fadeText = mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */);
@@ -891,7 +902,6 @@
}
});
mOpenCloseAnimator = closeAnim;
- closeAnim.playSequentially(arrowScale, revealAnim);
closeAnim.start();
mOriginalIcon.forceHideBadge(false);
}