• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 
17 package android.view;
18 
19 import android.util.Config;
20 import android.util.Log;
21 import android.util.Poolable;
22 import android.util.Pool;
23 import android.util.Pools;
24 import android.util.PoolableManager;
25 
26 /**
27  * Helper for tracking the velocity of touch events, for implementing
28  * flinging and other such gestures.  Use {@link #obtain} to retrieve a
29  * new instance of the class when you are going to begin tracking, put
30  * the motion events you receive into it with {@link #addMovement(MotionEvent)},
31  * and when you want to determine the velocity call
32  * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
33  * and {@link #getXVelocity()}.
34  */
35 public final class VelocityTracker implements Poolable<VelocityTracker> {
36     static final String TAG = "VelocityTracker";
37     static final boolean DEBUG = false;
38     static final boolean localLOGV = DEBUG || Config.LOGV;
39 
40     static final int NUM_PAST = 10;
41     static final int LONGEST_PAST_TIME = 200;
42 
43     static final VelocityTracker[] mPool = new VelocityTracker[1];
44     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
45             Pools.finitePool(new PoolableManager<VelocityTracker>() {
46                 public VelocityTracker newInstance() {
47                     return new VelocityTracker();
48                 }
49 
50                 public void onAcquired(VelocityTracker element) {
51                     element.clear();
52                 }
53 
54                 public void onReleased(VelocityTracker element) {
55                 }
56             }, 2));
57 
58     final float mPastX[] = new float[NUM_PAST];
59     final float mPastY[] = new float[NUM_PAST];
60     final long mPastTime[] = new long[NUM_PAST];
61 
62     float mYVelocity;
63     float mXVelocity;
64 
65     private VelocityTracker mNext;
66 
67     /**
68      * Retrieve a new VelocityTracker object to watch the velocity of a
69      * motion.  Be sure to call {@link #recycle} when done.  You should
70      * generally only maintain an active object while tracking a movement,
71      * so that the VelocityTracker can be re-used elsewhere.
72      *
73      * @return Returns a new VelocityTracker.
74      */
obtain()75     static public VelocityTracker obtain() {
76         return sPool.acquire();
77     }
78 
79     /**
80      * Return a VelocityTracker object back to be re-used by others.  You must
81      * not touch the object after calling this function.
82      */
recycle()83     public void recycle() {
84         sPool.release(this);
85     }
86 
87     /**
88      * @hide
89      */
setNextPoolable(VelocityTracker element)90     public void setNextPoolable(VelocityTracker element) {
91         mNext = element;
92     }
93 
94     /**
95      * @hide
96      */
getNextPoolable()97     public VelocityTracker getNextPoolable() {
98         return mNext;
99     }
100 
VelocityTracker()101     private VelocityTracker() {
102     }
103 
104     /**
105      * Reset the velocity tracker back to its initial state.
106      */
clear()107     public void clear() {
108         mPastTime[0] = 0;
109     }
110 
111     /**
112      * Add a user's movement to the tracker.  You should call this for the
113      * initial {@link MotionEvent#ACTION_DOWN}, the following
114      * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
115      * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
116      * for whichever events you desire.
117      *
118      * @param ev The MotionEvent you received and would like to track.
119      */
addMovement(MotionEvent ev)120     public void addMovement(MotionEvent ev) {
121         long time = ev.getEventTime();
122         final int N = ev.getHistorySize();
123         for (int i=0; i<N; i++) {
124             addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
125                     ev.getHistoricalEventTime(i));
126         }
127         addPoint(ev.getX(), ev.getY(), time);
128     }
129 
addPoint(float x, float y, long time)130     private void addPoint(float x, float y, long time) {
131         int drop = -1;
132         int i;
133         if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
134         final long[] pastTime = mPastTime;
135         for (i=0; i<NUM_PAST; i++) {
136             if (pastTime[i] == 0) {
137                 break;
138             } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
139                 if (localLOGV) Log.v(TAG, "Dropping past too old at "
140                         + i + " time=" + pastTime[i]);
141                 drop = i;
142             }
143         }
144         if (localLOGV) Log.v(TAG, "Add index: " + i);
145         if (i == NUM_PAST && drop < 0) {
146             drop = 0;
147         }
148         if (drop == i) drop--;
149         final float[] pastX = mPastX;
150         final float[] pastY = mPastY;
151         if (drop >= 0) {
152             if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
153             final int start = drop+1;
154             final int count = NUM_PAST-drop-1;
155             System.arraycopy(pastX, start, pastX, 0, count);
156             System.arraycopy(pastY, start, pastY, 0, count);
157             System.arraycopy(pastTime, start, pastTime, 0, count);
158             i -= (drop+1);
159         }
160         pastX[i] = x;
161         pastY[i] = y;
162         pastTime[i] = time;
163         i++;
164         if (i < NUM_PAST) {
165             pastTime[i] = 0;
166         }
167     }
168 
169     /**
170      * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
171      * velocity of Float.MAX_VALUE.
172      *
173      * @see #computeCurrentVelocity(int, float)
174      */
computeCurrentVelocity(int units)175     public void computeCurrentVelocity(int units) {
176         computeCurrentVelocity(units, Float.MAX_VALUE);
177     }
178 
179     /**
180      * Compute the current velocity based on the points that have been
181      * collected.  Only call this when you actually want to retrieve velocity
182      * information, as it is relatively expensive.  You can then retrieve
183      * the velocity with {@link #getXVelocity()} and
184      * {@link #getYVelocity()}.
185      *
186      * @param units The units you would like the velocity in.  A value of 1
187      * provides pixels per millisecond, 1000 provides pixels per second, etc.
188      * @param maxVelocity The maximum velocity that can be computed by this method.
189      * This value must be declared in the same unit as the units parameter. This value
190      * must be positive.
191      */
computeCurrentVelocity(int units, float maxVelocity)192     public void computeCurrentVelocity(int units, float maxVelocity) {
193         final float[] pastX = mPastX;
194         final float[] pastY = mPastY;
195         final long[] pastTime = mPastTime;
196 
197         // Kind-of stupid.
198         final float oldestX = pastX[0];
199         final float oldestY = pastY[0];
200         final long oldestTime = pastTime[0];
201         float accumX = 0;
202         float accumY = 0;
203         int N=0;
204         while (N < NUM_PAST) {
205             if (pastTime[N] == 0) {
206                 break;
207             }
208             N++;
209         }
210         // Skip the last received event, since it is probably pretty noisy.
211         if (N > 3) N--;
212 
213         for (int i=1; i < N; i++) {
214             final int dur = (int)(pastTime[i] - oldestTime);
215             if (dur == 0) continue;
216             float dist = pastX[i] - oldestX;
217             float vel = (dist/dur) * units;   // pixels/frame.
218             if (accumX == 0) accumX = vel;
219             else accumX = (accumX + vel) * .5f;
220 
221             dist = pastY[i] - oldestY;
222             vel = (dist/dur) * units;   // pixels/frame.
223             if (accumY == 0) accumY = vel;
224             else accumY = (accumY + vel) * .5f;
225         }
226         mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
227         mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
228 
229         if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
230                 + mXVelocity + " N=" + N);
231     }
232 
233     /**
234      * Retrieve the last computed X velocity.  You must first call
235      * {@link #computeCurrentVelocity(int)} before calling this function.
236      *
237      * @return The previously computed X velocity.
238      */
239     public float getXVelocity() {
240         return mXVelocity;
241     }
242 
243     /**
244      * Retrieve the last computed Y velocity.  You must first call
245      * {@link #computeCurrentVelocity(int)} before calling this function.
246      *
247      * @return The previously computed Y velocity.
248      */
249     public float getYVelocity() {
250         return mYVelocity;
251     }
252 }
253