VelocityTracker.java revision cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f
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[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     */
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     */
83    public void recycle() {
84        sPool.release(this);
85    }
86
87    /**
88     * @hide
89     */
90    public void setNextPoolable(VelocityTracker element) {
91        mNext = element;
92    }
93
94    /**
95     * @hide
96     */
97    public VelocityTracker getNextPoolable() {
98        return mNext;
99    }
100
101    private VelocityTracker() {
102    }
103
104    /**
105     * Reset the velocity tracker back to its initial state.
106     */
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     */
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
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     */
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     */
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