diff options
author | Jon Miranda <jonmiranda@google.com> | 2017-06-07 23:00:03 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-06-07 23:00:03 +0000 |
commit | 8dd654c9dd1314a9d0bf6e8066ea0274020e3c66 (patch) | |
tree | e515ccd17689a543a3532f3d2a277b72258093f1 | |
parent | ea98dd670350b2a31af5f8a4d2406bfe0110b290 (diff) | |
parent | 5c83e7cdc5f4d0406a5e564971871e1f694fddfc (diff) | |
download | android_packages_apps_Trebuchet-8dd654c9dd1314a9d0bf6e8066ea0274020e3c66.tar.gz android_packages_apps_Trebuchet-8dd654c9dd1314a9d0bf6e8066ea0274020e3c66.tar.bz2 android_packages_apps_Trebuchet-8dd654c9dd1314a9d0bf6e8066ea0274020e3c66.zip |
Refactor and generalize SpringAnimationHandler.
am: 5c83e7cdc5
Change-Id: I3fa69b3ab59badf589657a1dfc539481710e34b8
-rw-r--r-- | res/values/config.xml | 3 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AllAppsContainerView.java | 5 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AllAppsGridAdapter.java | 146 | ||||
-rw-r--r-- | src/com/android/launcher3/anim/SpringAnimationHandler.java | 176 |
4 files changed, 206 insertions, 124 deletions
diff --git a/res/values/config.xml b/res/values/config.xml index d2272f256..db1a75da9 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -64,6 +64,9 @@ <!-- The duration of the animation from search hint to text entry --> <integer name="config_searchHintAnimationDuration">50</integer> + <!-- View tag key used to store SpringAnimation data. --> + <item type="id" name="spring_animation_tag" /> + <!-- Workspace --> <!-- The duration (in ms) of the fade animation on the object outlines, used when we are dragging objects around on the home screen. --> diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index f1616fc09..c3df07360 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 d3d23ca24..9c7372f2c 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<AllAppsGridAdapter. */ public static class ViewHolder extends RecyclerView.ViewHolder { - /** - * Springs used for items where isViewType(viewType, VIEW_TYPE_MASK_HAS_SPRINGS) is true. - */ - private SpringAnimation spring; - public ViewHolder(View v) { super(v); } @@ -213,11 +210,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // The intent to send off to the market app, updated each time the search query changes. private Intent mMarketSearchIntent; - private SpringAnimationHandler mSpringAnimationHandler; + private SpringAnimationHandler<ViewHolder> 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<AllAppsGridAdapter. mLayoutInflater = LayoutInflater.from(launcher); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - mSpringAnimationHandler = springAnimationHandler; + if (FeatureFlags.LAUNCHER3_PHYSICS) { + mSpringAnimationHandler = new SpringAnimationHandler<>( + 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<AllAppsGridAdapter. R.layout.all_apps_icon, parent, false); icon.setOnClickListener(mIconClickListener); icon.setOnLongClickListener(mIconLongClickListener); - icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) - .getLongPressTimeout()); + icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout()); icon.setOnFocusChangeListener(mIconFocusListener); // Ensure the all apps icon height matches the workspace icons @@ -386,8 +388,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. public void onViewAttachedToWindow(ViewHolder holder) { int type = holder.getItemViewType(); if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { - holder.spring = mSpringAnimationHandler.add(holder.itemView, - holder.getAdapterPosition(), mApps, mAppsPerRow, holder.spring); + mSpringAnimationHandler.add(holder.itemView, holder); } } @@ -395,7 +396,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. public void onViewDetachedFromWindow(ViewHolder holder) { int type = holder.getItemViewType(); if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { - holder.spring = mSpringAnimationHandler.remove(holder.spring); + mSpringAnimationHandler.remove(holder.itemView); } } @@ -415,4 +416,121 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); return item.viewType; } + + /** + * Helper class to set the SpringAnimation values for an item in the adapter. + */ + private class AllAppsSpringAnimationFactory + implements SpringAnimationHandler.AnimationFactory<ViewHolder> { + 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 6a5e3514a..038f82682 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<T> { 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<T> mAnimationFactory; + private ArrayList<SpringAnimation> 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<T> 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,84 +160,55 @@ 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; + return mVelocityDirection == 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)); + private VelocityTracker getVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + return mVelocityTracker; } /** - * @return The app position is the position of the app in the Adapter if we ignored all other - * view types. + * This interface is used to initialize and update the SpringAnimations added to the + * {@link SpringAnimationHandler}. * - * ie. The first predicted app is at position 0, and the first app of all apps is - * at {@param appsPerRow}. + * @param <T> The object that each SpringAnimation is attached to. */ - 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; - } + public interface AnimationFactory<T> { + + /** + * Initializes a new Spring for {@param object}. + */ + SpringAnimation initialize(T object); - return appPosition; + /** + * Updates the value of {@param spring} based on {@param object}. + */ + void update(SpringAnimation spring, T object); } /** - * Increase the column factor as the distance increases between the column and the center - * column(s). + * Helper method to create a new SpringAnimation for {@param view}. */ - 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; + 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; } - private VelocityTracker getVelocityTracker() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - return mVelocityTracker; - } } |