VelocityTracker.java revision b79781af1ebc68dbaa1b44da43fd391de067a201
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    }
104
105    /**
106     * Reset the velocity tracker back to its initial state.
107     */
108    public void clear() {
109        final long[][] pastTime = mPastTime;
110        for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) {
111            for (int i = 0; i < NUM_PAST; i++) {
112                pastTime[p][i] = 0;
113            }
114        }
115    }
116
117    /**
118     * Add a user's movement to the tracker.  You should call this for the
119     * initial {@link MotionEvent#ACTION_DOWN}, the following
120     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
121     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
122     * for whichever events you desire.
123     *
124     * @param ev The MotionEvent you received and would like to track.
125     */
126    public void addMovement(MotionEvent ev) {
127        long time = ev.getEventTime();
128        final int N = ev.getHistorySize();
129        final int pointerCount = ev.getPointerCount();
130        for (int p = 0; p < pointerCount; p++) {
131            for (int i=0; i<N; i++) {
132                addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
133                        ev.getHistoricalEventTime(i));
134            }
135            addPoint(p, ev.getX(p), ev.getY(p), time);
136        }
137    }
138
139    private void addPoint(int pos, float x, float y, long time) {
140        final int lastTouch = (mLastTouch + 1) % NUM_PAST;
141        mPastX[pos][lastTouch] = x;
142        mPastY[pos][lastTouch] = y;
143        mPastTime[pos][lastTouch] = time;
144        mLastTouch = lastTouch;
145    }
146
147    /**
148     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
149     * velocity of Float.MAX_VALUE.
150     *
151     * @see #computeCurrentVelocity(int, float)
152     */
153    public void computeCurrentVelocity(int units) {
154        computeCurrentVelocity(units, Float.MAX_VALUE);
155    }
156
157    /**
158     * Compute the current velocity based on the points that have been
159     * collected.  Only call this when you actually want to retrieve velocity
160     * information, as it is relatively expensive.  You can then retrieve
161     * the velocity with {@link #getXVelocity()} and
162     * {@link #getYVelocity()}.
163     *
164     * @param units The units you would like the velocity in.  A value of 1
165     * provides pixels per millisecond, 1000 provides pixels per second, etc.
166     * @param maxVelocity The maximum velocity that can be computed by this method.
167     * This value must be declared in the same unit as the units parameter. This value
168     * must be positive.
169     */
170    public void computeCurrentVelocity(int units, float maxVelocity) {
171        for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
172            final float[] pastX = mPastX[pos];
173            final float[] pastY = mPastY[pos];
174            final long[] pastTime = mPastTime[pos];
175            final int lastTouch = mLastTouch;
176
177            // find oldest acceptable time
178            int oldestTouch = lastTouch;
179            if (pastTime[lastTouch] > 0) { // cleared ?
180                oldestTouch = (lastTouch + 1) % NUM_PAST;
181                final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME;
182                while (pastTime[oldestTouch] < acceptableTime) {
183                    oldestTouch = (oldestTouch + 1) % NUM_PAST;
184                }
185            }
186
187            // Kind-of stupid.
188            final float oldestX = pastX[oldestTouch];
189            final float oldestY = pastY[oldestTouch];
190            final long oldestTime = pastTime[oldestTouch];
191            float accumX = 0;
192            float accumY = 0;
193            int N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1;
194            // Skip the last received event, since it is probably pretty noisy.
195            if (N > 3) N--;
196
197            for (int i=1; i < N; i++) {
198                final int j = (oldestTouch + i) % NUM_PAST;
199                final int dur = (int)(pastTime[j] - oldestTime);
200                if (dur == 0) continue;
201                float dist = pastX[j] - oldestX;
202                float vel = (dist/dur) * units;   // pixels/frame.
203                accumX = (accumX == 0) ? vel : (accumX + vel) * .5f;
204
205                dist = pastY[j] - oldestY;
206                vel = (dist/dur) * units;   // pixels/frame.
207                accumY = (accumY == 0) ? vel : (accumY + vel) * .5f;
208            }
209
210            mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
211                    : Math.min(accumX, maxVelocity);
212            mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
213                    : Math.min(accumY, maxVelocity);
214
215            if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
216                    + mXVelocity + " N=" + N);
217        }
218    }
219
220    /**
221     * Retrieve the last computed X velocity.  You must first call
222     * {@link #computeCurrentVelocity(int)} before calling this function.
223     *
224     * @return The previously computed X velocity.
225     */
226    public float getXVelocity() {
227        return mXVelocity[0];
228    }
229
230    /**
231     * Retrieve the last computed Y velocity.  You must first call
232     * {@link #computeCurrentVelocity(int)} before calling this function.
233     *
234     * @return The previously computed Y velocity.
235     */
236    public float getYVelocity() {
237        return mYVelocity[0];
238    }
239
240    /**
241     * Retrieve the last computed X velocity.  You must first call
242     * {@link #computeCurrentVelocity(int)} before calling this function.
243     *
244     * @param pos Which pointer's velocity to return.
245     * @return The previously computed X velocity.
246     *
247     * @hide Pending API approval
248     */
249    public float getXVelocity(int pos) {
250        return mXVelocity[pos];
251    }
252
253    /**
254     * Retrieve the last computed Y velocity.  You must first call
255     * {@link #computeCurrentVelocity(int)} before calling this function.
256     *
257     * @param pos Which pointer's velocity to return.
258     * @return The previously computed Y velocity.
259     *
260     * @hide Pending API approval
261     */
262    public float getYVelocity(int pos) {
263        return mYVelocity[pos];
264    }
265}
266