diff options
author | Jon Miranda <jonmiranda@google.com> | 2017-04-20 12:07:38 -0700 |
---|---|---|
committer | Jon Miranda <jonmiranda@google.com> | 2017-05-23 10:02:51 -0700 |
commit | 4e11c2738a346fe2859c2d03e3133671e5da49ec (patch) | |
tree | 5570fa051502b27134263092c4030bb4102c040b /src/com/android/launcher3/anim/SpringAnimationHandler.java | |
parent | 09273b676e9659890651459c11bda73d67e925c0 (diff) | |
download | android_packages_apps_Trebuchet-4e11c2738a346fe2859c2d03e3133671e5da49ec.tar.gz android_packages_apps_Trebuchet-4e11c2738a346fe2859c2d03e3133671e5da49ec.tar.bz2 android_packages_apps_Trebuchet-4e11c2738a346fe2859c2d03e3133671e5da49ec.zip |
Add physics motion to items in all apps.
Motion applied to:
- Icons
- Prediction icons
- Prediction divider
Bug: 38349031
Change-Id: I376e6e39080c8c80463a0ce8b104b05e4d576f17
Diffstat (limited to 'src/com/android/launcher3/anim/SpringAnimationHandler.java')
-rw-r--r-- | src/com/android/launcher3/anim/SpringAnimationHandler.java | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java new file mode 100644 index 000000000..5792127d0 --- /dev/null +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.anim; + +import android.support.animation.DynamicAnimation; +import android.support.animation.SpringAnimation; +import android.support.animation.SpringForce; +import android.support.annotation.IntDef; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; + +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AlphabeticalAppsList; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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. + */ +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; + + @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 VelocityTracker mVelocityTracker; + private float mCurrentVelocity = 0; + private boolean mShouldComputeVelocity = false; + + private ArrayList<SpringAnimation> mAnimations = new ArrayList<>(); + + public SpringAnimationHandler(@Direction int direction) { + mDirection = direction; + mVelocityTracker = VelocityTracker.obtain(); + } + + 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); + } + + // 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; + } + + public SpringAnimation remove(SpringAnimation animation) { + animation.skipToEnd(); + mAnimations.remove(animation); + return animation; + } + + public void addMovement(MotionEvent event) { + int action = event.getActionMasked(); + if (DEBUG) Log.d(TAG, "addMovement#action=" + action); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_DOWN: + reset(); + break; + } + + getVelocityTracker().addMovement(event); + mShouldComputeVelocity = true; + } + + public void animateToFinalPosition(float position) { + if (DEBUG) Log.d(TAG, "animateToFinalPosition#computeVelocity=" + mShouldComputeVelocity); + + if (mShouldComputeVelocity) { + computeVelocity(); + setStartVelocity(mCurrentVelocity); + } + + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + mAnimations.get(i).animateToFinalPosition(position); + } + + reset(); + } + + public void skipToEnd() { + if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd"); + if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception()); + + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + mAnimations.get(i).skipToEnd(); + } + } + + public void reset() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + mCurrentVelocity = 0; + } + + private void setStartVelocity(float velocity) { + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + mAnimations.get(i).setStartVelocity(velocity); + } + } + + private void computeVelocity() { + getVelocityTracker().computeCurrentVelocity(300); + + mCurrentVelocity = isVerticalDirection() + ? getVelocityTracker().getYVelocity() + : getVelocityTracker().getXVelocity(); + 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; + } + + private VelocityTracker getVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + return mVelocityTracker; + } +} |