From 5c83e7cdc5f4d0406a5e564971871e1f694fddfc Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 6 Jun 2017 15:41:21 -0700 Subject: [PATCH] Refactor and generalize SpringAnimationHandler. SpringAnimationHandler is only responsible for motion tracking and updating the list of springs it is handling. Spring initialization and updates are handled by the AnimationFactory interface. This is done in preparation for future CLs where SpringAnimationHandler will be used in more cases. Bug: 38349031 Change-Id: I5ebce710cc05e4e88b708ceea5d2cfefbd0eb3d0 --- res/values/config.xml | 3 + .../allapps/AllAppsContainerView.java | 5 +- .../launcher3/allapps/AllAppsGridAdapter.java | 146 ++++++++++++-- .../anim/SpringAnimationHandler.java | 188 +++++++----------- 4 files changed, 212 insertions(+), 130 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index d2272f256b..db1a75da91 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -64,6 +64,9 @@ 50 + + + diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index f1616fc09f..c3df07360e 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -91,9 +91,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mLauncher = Launcher.getLauncher(context); mApps = new AlphabeticalAppsList(context); - mSpringAnimationHandler = new SpringAnimationHandler(SpringAnimationHandler.Y_DIRECTION); - mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this, - mSpringAnimationHandler); + mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this); + mSpringAnimationHandler = mAdapter.getSpringAnimationHandler(); mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mSearchQueryBuilder = new SpannableStringBuilder(); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index d3d23ca249..9c7372f2ca 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Point; +import android.support.animation.DynamicAnimation; import android.support.animation.SpringAnimation; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; @@ -38,6 +39,7 @@ import com.android.launcher3.AppInfo; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.config.FeatureFlags; @@ -96,11 +98,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter mSpringAnimationHandler; public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener - iconClickListener, View.OnLongClickListener iconLongClickListener, - SpringAnimationHandler springAnimationHandler) { + iconClickListener, View.OnLongClickListener iconLongClickListener) { Resources res = launcher.getResources(); mLauncher = launcher; mApps = apps; @@ -228,7 +224,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter( + SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory()); + } + } + + public SpringAnimationHandler getSpringAnimationHandler() { + return mSpringAnimationHandler; } public static boolean isDividerViewType(int viewType) { @@ -292,8 +295,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter { + private static final float DEFAULT_MAX_VALUE_PX = 100; + private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX; + + // Damping ratio range is [0, 1] + private static final float SPRING_DAMPING_RATIO = 0.55f; + + // Stiffness is a non-negative number. + private static final float MIN_SPRING_STIFFNESS = 580f; + private static final float MAX_SPRING_STIFFNESS = 900f; + + // The amount by which each adjacent rows' stiffness will differ. + private static final float ROW_STIFFNESS_COEFFICIENT = 50f; + + @Override + public SpringAnimation initialize(ViewHolder vh) { + return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0); + } + + /** + * @param spring A new or recycled SpringAnimation. + * @param vh The ViewHolder that {@param spring} is related to. + */ + @Override + public void update(SpringAnimation spring, ViewHolder vh) { + int numPredictedApps = Math.min(mAppsPerRow, mApps.getPredictedApps().size()); + int appPosition = getAppPosition(vh.getAdapterPosition(), numPredictedApps, + mAppsPerRow); + + int col = appPosition % mAppsPerRow; + int row = appPosition / mAppsPerRow; + + int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count + if (row > (numTotalRows / 2)) { + // Mirror the rows so that the top row acts the same as the bottom row. + row = Math.abs(numTotalRows - row); + } + + // We manipulate the stiffness, min, and max values based on the items distance to the + // first row and the items distance to the center column to create the ^-shaped motion + // effect. + float rowFactor = (1 + row) * 0.5f; + float colFactor = getColumnFactor(col, mAppsPerRow); + + float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor); + float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor); + + float stiffness = Utilities.boundToRange( + MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT), + MIN_SPRING_STIFFNESS, + MAX_SPRING_STIFFNESS); + + spring.setMinValue(minValue) + .setMaxValue(maxValue) + .getSpring() + .setStiffness(stiffness) + .setDampingRatio(SPRING_DAMPING_RATIO); + } + + /** + * @return The app position is the position of the app in the Adapter if we ignored all + * other view types. + * + * The first app is at position 0, and the first app each following row is at a + * position that is a multiple of {@param appsPerRow}. + * + * ie. If there are 5 apps per row, and there are two rows of apps: + * 0 1 2 3 4 + * 5 6 7 8 9 + */ + private int getAppPosition(int position, int numPredictedApps, int appsPerRow) { + int appPosition = position; + int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1); + + int allAppsStartAt = numDividerViews + numPredictedApps; + if (numDividerViews == 1 || position < allAppsStartAt) { + appPosition -= 1; + } else { + // We cannot assume that the predicted row will always be full. + int numPredictedAppsOffset = appsPerRow - numPredictedApps; + appPosition = position + numPredictedAppsOffset - numDividerViews; + } + + return appPosition; + } + + /** + * Increase the column factor as the distance increases between the column and the center + * column(s). + */ + private float getColumnFactor(int col, int numCols) { + float centerColumn = numCols / 2; + int distanceToCenter = (int) Math.abs(col - centerColumn); + + boolean evenNumberOfColumns = numCols % 2 == 0; + if (evenNumberOfColumns && col < centerColumn) { + distanceToCenter -= 1; + } + + float factor = 0; + while (distanceToCenter > 0) { + if (distanceToCenter == 1) { + factor += 0.2f; + } else { + factor += 0.1f; + } + --distanceToCenter; + } + + return factor; + } + } } diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java index 6a5e3514a1..038f82682c 100644 --- a/src/com/android/launcher3/anim/SpringAnimationHandler.java +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -15,7 +15,7 @@ */ package com.android.launcher3.anim; -import android.support.animation.DynamicAnimation; +import android.support.animation.FloatPropertyCompat; import android.support.animation.SpringAnimation; import android.support.animation.SpringForce; import android.support.annotation.IntDef; @@ -24,8 +24,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,77 +34,67 @@ import java.util.ArrayList; * Handler class that manages springs for a set of views that should all move based on the same * {@link MotionEvent}s. * - * Supports using physics for X or Y translations. + * Supports setting either X or Y velocity on the list of springs added to this handler. */ -public class SpringAnimationHandler { +public class SpringAnimationHandler { private static final String TAG = "SpringAnimationHandler"; private static final boolean DEBUG = false; - private static final float DEFAULT_MAX_VALUE = 100; - private static final float DEFAULT_MIN_VALUE = -DEFAULT_MAX_VALUE; - - private static final float SPRING_DAMPING_RATIO = 0.55f; - private static final float MIN_SPRING_STIFFNESS = 580f; - private static final float MAX_SPRING_STIFFNESS = 900f; + private static final float VELOCITY_DAMPING_FACTOR = 0.175f; @Retention(RetentionPolicy.SOURCE) @IntDef({Y_DIRECTION, X_DIRECTION}) public @interface Direction {} public static final int Y_DIRECTION = 0; public static final int X_DIRECTION = 1; - private int mDirection; + private int mVelocityDirection; private VelocityTracker mVelocityTracker; private float mCurrentVelocity = 0; private boolean mShouldComputeVelocity = false; + private AnimationFactory mAnimationFactory; + private ArrayList mAnimations = new ArrayList<>(); - public SpringAnimationHandler(@Direction int direction) { - mDirection = direction; - mVelocityTracker = VelocityTracker.obtain(); + /** + * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}. + * Determines which direction we use to calculate and set the velocity. + * @param factory The AnimationFactory is responsible for initializing and updating the + * SpringAnimations added to this class. + */ + public SpringAnimationHandler(@Direction int direction, AnimationFactory factory) { + mVelocityDirection = direction; + mAnimationFactory = factory; } - public SpringAnimation add(View view, int position, AlphabeticalAppsList apps, int appsPerRow, - SpringAnimation recycle) { - int numPredictedApps = Math.min(appsPerRow, apps.getPredictedApps().size()); - int appPosition = getAppPosition(position, numPredictedApps, appsPerRow); - - int col = appPosition % appsPerRow; - int row = appPosition / appsPerRow; - - int numTotalRows = apps.getNumAppRows() - 1; // zero offset - if (row > (numTotalRows / 2)) { - // Mirror the rows so that the top row acts the same as the bottom row. - row = Math.abs(numTotalRows - row); + /** + * Adds a new or recycled animation to the list of springs handled by this class. + * + * @param view The view the spring is attached to. + * @param object Used to initialize and update the spring. + */ + public void add(View view, T object) { + SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag); + if (spring == null) { + spring = mAnimationFactory.initialize(object); + view.setTag(R.id.spring_animation_tag, spring); } - - // We manipulate the stiffness, min, and max values based on the items distance to the first - // row and the items distance to the center column to create the ^-shaped motion effect. - float rowFactor = (1 + row) * 0.5f; - float colFactor = getColumnFactor(col, appsPerRow); - - float minValue = DEFAULT_MIN_VALUE * (rowFactor + colFactor); - float maxValue = DEFAULT_MAX_VALUE * (rowFactor + colFactor); - - float stiffness = Utilities.boundToRange(MAX_SPRING_STIFFNESS - (row * 50f), - MIN_SPRING_STIFFNESS, MAX_SPRING_STIFFNESS); - - SpringAnimation animation = (recycle != null ? recycle : createSpringAnimation(view)) - .setStartVelocity(mCurrentVelocity) - .setMinValue(minValue) - .setMaxValue(maxValue); - animation.getSpring().setStiffness(stiffness); - - mAnimations.add(animation); - return animation; + mAnimationFactory.update(spring, object); + spring.setStartVelocity(mCurrentVelocity); + mAnimations.add(spring); } - public SpringAnimation remove(SpringAnimation animation) { - animation.skipToEnd(); + /** + * Stops and removes the spring attached to {@param view}. + */ + public void remove(View view) { + SpringAnimation animation = (SpringAnimation) view.getTag(R.id.spring_animation_tag); + if (animation.canSkipToEnd()) { + animation.skipToEnd(); + } mAnimations.remove(animation); - return animation; } public void addMovement(MotionEvent event) { @@ -149,7 +138,9 @@ public class SpringAnimationHandler { int size = mAnimations.size(); for (int i = 0; i < size; ++i) { - mAnimations.get(i).skipToEnd(); + if (mAnimations.get(i).canSkipToEnd()) { + mAnimations.get(i).skipToEnd(); + } } } @@ -169,78 +160,19 @@ public class SpringAnimationHandler { } private void computeVelocity() { - getVelocityTracker().computeCurrentVelocity(175); + getVelocityTracker().computeCurrentVelocity(1000 /* millis */); mCurrentVelocity = isVerticalDirection() ? getVelocityTracker().getYVelocity() : getVelocityTracker().getXVelocity(); + mCurrentVelocity *= VELOCITY_DAMPING_FACTOR; mShouldComputeVelocity = false; if (DEBUG) Log.d(TAG, "computeVelocity=" + mCurrentVelocity); } private boolean isVerticalDirection() { - return mDirection == Y_DIRECTION; - } - - private SpringAnimation createSpringAnimation(View view) { - DynamicAnimation.ViewProperty property = isVerticalDirection() - ? DynamicAnimation.TRANSLATION_Y - : DynamicAnimation.TRANSLATION_X; - - return new SpringAnimation(view, property, 0) - .setStartValue(1f) - .setSpring(new SpringForce(0) - .setDampingRatio(SPRING_DAMPING_RATIO)); - } - - /** - * @return The app position is the position of the app in the Adapter if we ignored all other - * view types. - * - * ie. The first predicted app is at position 0, and the first app of all apps is - * at {@param appsPerRow}. - */ - private int getAppPosition(int position, int numPredictedApps, int appsPerRow) { - int appPosition = position; - int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1); - - int allAppsStartAt = numDividerViews + numPredictedApps; - if (numDividerViews == 1 || position < allAppsStartAt) { - appPosition -= 1; - } else { - // We cannot assume that the predicted row will always be full. - int numPredictedAppsOffset = appsPerRow - numPredictedApps; - appPosition = position + numPredictedAppsOffset - numDividerViews; - } - - return appPosition; - } - - /** - * Increase the column factor as the distance increases between the column and the center - * column(s). - */ - private float getColumnFactor(int col, int numCols) { - float centerColumn = numCols / 2; - int distanceToCenter = (int) Math.abs(col - centerColumn); - - boolean evenNumberOfColumns = numCols % 2 == 0; - if (evenNumberOfColumns && col < centerColumn) { - distanceToCenter -= 1; - } - - float factor = 0; - while (distanceToCenter > 0) { - if (distanceToCenter == 1) { - factor += 0.2f; - } else { - factor += 0.1f; - } - --distanceToCenter; - } - - return factor; + return mVelocityDirection == Y_DIRECTION; } private VelocityTracker getVelocityTracker() { @@ -249,4 +181,34 @@ public class SpringAnimationHandler { } return mVelocityTracker; } + + /** + * This interface is used to initialize and update the SpringAnimations added to the + * {@link SpringAnimationHandler}. + * + * @param The object that each SpringAnimation is attached to. + */ + public interface AnimationFactory { + + /** + * Initializes a new Spring for {@param object}. + */ + SpringAnimation initialize(T object); + + /** + * Updates the value of {@param spring} based on {@param object}. + */ + void update(SpringAnimation spring, T object); + } + + /** + * Helper method to create a new SpringAnimation for {@param view}. + */ + 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; + } + }