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
This commit is contained in:
Jon Miranda
2017-06-06 15:41:21 -07:00
parent 3a5b835297
commit 5c83e7cdc5
4 changed files with 212 additions and 130 deletions

View File

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