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; + } + }