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