VelocityTracker.java revision b1110149cccc3b99e59ead34ca46e5ac026f6db9
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
17package android.view;
18
19import android.util.Config;
20import android.util.Log;
21import android.util.Poolable;
22import android.util.Pool;
23import android.util.Pools;
24import 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 */
35public 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[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
59    final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
60    final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
61
62    float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
63    float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
64    int mLastTouch;
65
66    private VelocityTracker mNext;
67
68    /**
69     * Retrieve a new VelocityTracker object to watch the velocity of a
70     * motion.  Be sure to call {@link #recycle} when done.  You should
71     * generally only maintain an active object while tracking a movement,
72     * so that the VelocityTracker can be re-used elsewhere.
73     *
74     * @return Returns a new VelocityTracker.
75     */
76    static public VelocityTracker obtain() {
77        return sPool.acquire();
78    }
79
80    /**
81     * Return a VelocityTracker object back to be re-used by others.  You must
82     * not touch the object after calling this function.
83     */
84    public void recycle() {
85        sPool.release(this);
86    }
87
88    /**
89     * @hide
90     */
91    public void setNextPoolable(VelocityTracker element) {
92        mNext = element;
93    }
94
95    /**
96     * @hide
97     */
98    public VelocityTracker getNextPoolable() {
99        return mNext;
100    }
101
102    private VelocityTracker() {
103        clear();
104    }
105
106    /**
107     * Reset the velocity tracker back to its initial state.
108     */
109    public void clear() {
110        final long[][] pastTime = mPastTime;
111        for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) {
112            for (int i = 0; i < NUM_PAST; i++) {
113                pastTime[p][i] = Long.MIN_VALUE;
114            }
115        }
116    }
117
118    /**
119     * Add a user's movement to the tracker.  You should call this for the
120     * initial {@link MotionEvent#ACTION_DOWN}, the following
121     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
122     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
123     * for whichever events you desire.
124     *
125     * @param ev The MotionEvent you received and would like to track.
126     */
127    public void addMovement(MotionEvent ev) {
128        final int N = ev.getHistorySize();
129        final int pointerCount = ev.getPointerCount();
130        int touchIndex = (mLastTouch + 1) % NUM_PAST;
131        for (int i=0; i<N; i++) {
132            for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
133                mPastTime[id][touchIndex] = Long.MIN_VALUE;
134            }
135            for (int p = 0; p < pointerCount; p++) {
136                int id = ev.getPointerId(p);
137                mPastX[id][touchIndex] = ev.getHistoricalX(p, i);
138                mPastY[id][touchIndex] = ev.getHistoricalY(p, i);
139                mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i);
140            }
141
142            touchIndex = (touchIndex + 1) % NUM_PAST;
143        }
144
145        // During calculation any pointer values with a time of MIN_VALUE are treated
146        // as a break in input. Initialize all to MIN_VALUE for each new touch index.
147        for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
148            mPastTime[id][touchIndex] = Long.MIN_VALUE;
149        }
150        final long time = ev.getEventTime();
151        for (int p = 0; p < pointerCount; p++) {
152            int id = ev.getPointerId(p);
153            mPastX[id][touchIndex] = ev.getX(p);
154            mPastY[id][touchIndex] = ev.getY(p);
155            mPastTime[id][touchIndex] = time;
156        }
157
158        mLastTouch = touchIndex;
159    }
160
161    /**
162     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
163     * velocity of Float.MAX_VALUE.
164     *
165     * @see #computeCurrentVelocity(int, float)
166     */
167    public void computeCurrentVelocity(int units) {
168        computeCurrentVelocity(units, Float.MAX_VALUE);
169    }
170
171    /**
172     * Compute the current velocity based on the points that have been
173     * collected.  Only call this when you actually want to retrieve velocity
174     * information, as it is relatively expensive.  You can then retrieve
175     * the velocity with {@link #getXVelocity()} and
176     * {@link #getYVelocity()}.
177     *
178     * @param units The units you would like the velocity in.  A value of 1
179     * provides pixels per millisecond, 1000 provides pixels per second, etc.
180     * @param maxVelocity The maximum velocity that can be computed by this method.
181     * This value must be declared in the same unit as the units parameter. This value
182     * must be positive.
183     */
184    public void computeCurrentVelocity(int units, float maxVelocity) {
185        for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
186            final float[] pastX = mPastX[pos];
187            final float[] pastY = mPastY[pos];
188            final long[] pastTime = mPastTime[pos];
189            final int lastTouch = mLastTouch;
190
191            // find oldest acceptable time
192            int oldestTouch = lastTouch;
193            if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ?
194                final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME;
195                int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
196                while (pastTime[nextOldestTouch] >= acceptableTime &&
197                        nextOldestTouch != lastTouch) {
198                    oldestTouch = nextOldestTouch;
199                    nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
200                }
201            }
202
203            // Kind-of stupid.
204            final float oldestX = pastX[oldestTouch];
205            final float oldestY = pastY[oldestTouch];
206            final long oldestTime = pastTime[oldestTouch];
207            float accumX = 0;
208            float accumY = 0;
209            int N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1;
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 j = (oldestTouch + i) % NUM_PAST;
215                final int dur = (int)(pastTime[j] - oldestTime);
216                if (dur == 0) continue;
217                float dist = pastX[j] - oldestX;
218                float vel = (dist/dur) * units;   // pixels/frame.
219                accumX = (accumX == 0) ? vel : (accumX + vel) * .5f;
220
221                dist = pastY[j] - oldestY;
222                vel = (dist/dur) * units;   // pixels/frame.
223                accumY = (accumY == 0) ? vel : (accumY + vel) * .5f;
224            }
225
226            mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
227                    : Math.min(accumX, maxVelocity);
228            mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
229                    : Math.min(accumY, maxVelocity);
230
231            if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
232                    + mXVelocity + " N=" + N);
233        }
234    }
235
236    /**
237     * Retrieve the last computed X velocity.  You must first call
238     * {@link #computeCurrentVelocity(int)} before calling this function.
239     *
240     * @return The previously computed X velocity.
241     */
242    public float getXVelocity() {
243        return mXVelocity[0];
244    }
245
246    /**
247     * Retrieve the last computed Y velocity.  You must first call
248     * {@link #computeCurrentVelocity(int)} before calling this function.
249     *
250     * @return The previously computed Y velocity.
251     */
252    public float getYVelocity() {
253        return mYVelocity[0];
254    }
255
256    /**
257     * Retrieve the last computed X velocity.  You must first call
258     * {@link #computeCurrentVelocity(int)} before calling this function.
259     *
260     * @param id Which pointer's velocity to return.
261     * @return The previously computed X velocity.
262     */
263    public float getXVelocity(int id) {
264        return mXVelocity[id];
265    }
266
267    /**
268     * Retrieve the last computed Y velocity.  You must first call
269     * {@link #computeCurrentVelocity(int)} before calling this function.
270     *
271     * @param id Which pointer's velocity to return.
272     * @return The previously computed Y velocity.
273     */
274    public float getYVelocity(int id) {
275        return mYVelocity[id];
276    }
277}
278