1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.anim; 17 18 import android.support.animation.FloatPropertyCompat; 19 import android.support.animation.SpringAnimation; 20 import android.support.animation.SpringForce; 21 import android.support.annotation.IntDef; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 import android.view.VelocityTracker; 25 import android.view.View; 26 27 import com.android.launcher3.R; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.ArrayList; 32 33 /** 34 * Handler class that manages springs for a set of views that should all move based on the same 35 * {@link MotionEvent}s. 36 * 37 * Supports setting either X or Y velocity on the list of springs added to this handler. 38 */ 39 public class SpringAnimationHandler<T> { 40 41 private static final String TAG = "SpringAnimationHandler"; 42 private static final boolean DEBUG = false; 43 44 private static final float VELOCITY_DAMPING_FACTOR = 0.175f; 45 46 @Retention(RetentionPolicy.SOURCE) 47 @IntDef({Y_DIRECTION, X_DIRECTION}) 48 public @interface Direction {} 49 public static final int Y_DIRECTION = 0; 50 public static final int X_DIRECTION = 1; 51 private int mVelocityDirection; 52 53 private VelocityTracker mVelocityTracker; 54 private float mCurrentVelocity = 0; 55 private boolean mShouldComputeVelocity = false; 56 57 private AnimationFactory<T> mAnimationFactory; 58 59 private ArrayList<SpringAnimation> mAnimations = new ArrayList<>(); 60 61 /** 62 * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}. 63 * Determines which direction we use to calculate and set the velocity. 64 * @param factory The AnimationFactory is responsible for initializing and updating the 65 * SpringAnimations added to this class. 66 */ SpringAnimationHandler(@irection int direction, AnimationFactory<T> factory)67 public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) { 68 mVelocityDirection = direction; 69 mAnimationFactory = factory; 70 } 71 72 /** 73 * Adds a spring to the list of springs handled by this class. 74 * @param spring The new spring to be added. 75 * @param setDefaultValues If True, sets the spring to the default 76 * {@link AnimationFactory} values. 77 */ add(SpringAnimation spring, boolean setDefaultValues)78 public void add(SpringAnimation spring, boolean setDefaultValues) { 79 if (setDefaultValues) { 80 mAnimationFactory.setDefaultValues(spring); 81 } 82 spring.setStartVelocity(mCurrentVelocity); 83 mAnimations.add(spring); 84 } 85 86 /** 87 * Adds a new or recycled animation to the list of springs handled by this class. 88 * 89 * @param view The view the spring is attached to. 90 * @param object Used to initialize and update the spring. 91 */ add(View view, T object)92 public void add(View view, T object) { 93 SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag); 94 if (spring == null) { 95 spring = mAnimationFactory.initialize(object); 96 view.setTag(R.id.spring_animation_tag, spring); 97 } 98 mAnimationFactory.update(spring, object); 99 add(spring, false /* setDefaultValues */); 100 } 101 102 /** 103 * Stops and removes the spring attached to {@param view}. 104 */ remove(View view)105 public void remove(View view) { 106 remove((SpringAnimation) view.getTag(R.id.spring_animation_tag)); 107 } 108 remove(SpringAnimation animation)109 public void remove(SpringAnimation animation) { 110 if (animation.canSkipToEnd()) { 111 animation.skipToEnd(); 112 } 113 mAnimations.remove(animation); 114 } 115 addMovement(MotionEvent event)116 public void addMovement(MotionEvent event) { 117 int action = event.getActionMasked(); 118 if (DEBUG) Log.d(TAG, "addMovement#action=" + action); 119 switch (action) { 120 case MotionEvent.ACTION_CANCEL: 121 case MotionEvent.ACTION_DOWN: 122 reset(); 123 break; 124 } 125 126 getVelocityTracker().addMovement(event); 127 mShouldComputeVelocity = true; 128 } 129 animateToFinalPosition(float position, int startValue)130 public void animateToFinalPosition(float position, int startValue) { 131 animateToFinalPosition(position, startValue, mShouldComputeVelocity); 132 } 133 134 /** 135 * @param position The final animation position. 136 * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start 137 * The magnitude of the number changes how the spring will move. 138 * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before 139 * starting the animation. 140 */ animateToFinalPosition(float position, int startValue, boolean setVelocity)141 private void animateToFinalPosition(float position, int startValue, boolean setVelocity) { 142 if (DEBUG) { 143 Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue); 144 } 145 146 if (mShouldComputeVelocity) { 147 mCurrentVelocity = computeVelocity(); 148 } 149 150 int size = mAnimations.size(); 151 for (int i = 0; i < size; ++i) { 152 mAnimations.get(i).setStartValue(startValue); 153 if (setVelocity) { 154 mAnimations.get(i).setStartVelocity(mCurrentVelocity); 155 } 156 mAnimations.get(i).animateToFinalPosition(position); 157 } 158 159 reset(); 160 } 161 162 /** 163 * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to 164 * manually set the velocity. 165 */ animateToPositionWithVelocity(float position, int startValue, float velocity)166 public void animateToPositionWithVelocity(float position, int startValue, float velocity) { 167 if (DEBUG) { 168 Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue 169 + ", velocity=" + velocity); 170 } 171 172 mCurrentVelocity = velocity; 173 mShouldComputeVelocity = false; 174 animateToFinalPosition(position, startValue, true); 175 } 176 177 isRunning()178 public boolean isRunning() { 179 // All the animations run at the same time so we can just check the first one. 180 return !mAnimations.isEmpty() && mAnimations.get(0).isRunning(); 181 } 182 skipToEnd()183 public void skipToEnd() { 184 if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd"); 185 if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception()); 186 187 int size = mAnimations.size(); 188 for (int i = 0; i < size; ++i) { 189 if (mAnimations.get(i).canSkipToEnd()) { 190 mAnimations.get(i).skipToEnd(); 191 } 192 } 193 } 194 reset()195 public void reset() { 196 if (mVelocityTracker != null) { 197 mVelocityTracker.recycle(); 198 mVelocityTracker = null; 199 } 200 mCurrentVelocity = 0; 201 mShouldComputeVelocity = false; 202 } 203 204 computeVelocity()205 private float computeVelocity() { 206 getVelocityTracker().computeCurrentVelocity(1000 /* millis */); 207 208 float velocity = isVerticalDirection() 209 ? getVelocityTracker().getYVelocity() 210 : getVelocityTracker().getXVelocity(); 211 velocity *= VELOCITY_DAMPING_FACTOR; 212 213 if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity); 214 return velocity; 215 } 216 isVerticalDirection()217 private boolean isVerticalDirection() { 218 return mVelocityDirection == Y_DIRECTION; 219 } 220 getVelocityTracker()221 private VelocityTracker getVelocityTracker() { 222 if (mVelocityTracker == null) { 223 mVelocityTracker = VelocityTracker.obtain(); 224 } 225 return mVelocityTracker; 226 } 227 228 /** 229 * This interface is used to initialize and update the SpringAnimations added to the 230 * {@link SpringAnimationHandler}. 231 * 232 * @param <T> The object that each SpringAnimation is attached to. 233 */ 234 public interface AnimationFactory<T> { 235 236 /** 237 * Initializes a new Spring for {@param object}. 238 */ initialize(T object)239 SpringAnimation initialize(T object); 240 241 /** 242 * Updates the value of {@param spring} based on {@param object}. 243 */ update(SpringAnimation spring, T object)244 void update(SpringAnimation spring, T object); 245 246 /** 247 * Sets the factory default values for the given {@param spring}. 248 */ setDefaultValues(SpringAnimation spring)249 void setDefaultValues(SpringAnimation spring); 250 } 251 252 /** 253 * Helper method to create a new SpringAnimation for {@param view}. 254 */ forView(View view, FloatPropertyCompat property, float finalPos)255 public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) { 256 SpringAnimation spring = new SpringAnimation(view, property, finalPos); 257 spring.setSpring(new SpringForce(finalPos)); 258 return spring; 259 } 260 261 } 262