• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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