blob: c80167ef5542f71b470aebd4a33500841e2158f9 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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 return vt;
62 }
63 return new VelocityTracker();
64 }
65 }
66
67 /**
68 * Return a VelocityTracker object back to be re-used by others. You must
69 * not touch the object after calling this function.
70 */
71 public void recycle() {
72 synchronized (mPool) {
73 mPool[0] = this;
74 }
75 }
76
77 private VelocityTracker() {
78 }
79
80 /**
81 * Reset the velocity tracker back to its initial state.
82 */
83 public void clear() {
84 mPastTime[0] = 0;
85 }
86
87 /**
88 * Add a user's movement to the tracker. You should call this for the
89 * initial {@link MotionEvent#ACTION_DOWN}, the following
90 * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
91 * final {@link MotionEvent#ACTION_UP}. You can, however, call this
92 * for whichever events you desire.
93 *
94 * @param ev The MotionEvent you received and would like to track.
95 */
96 public void addMovement(MotionEvent ev) {
97 long time = ev.getEventTime();
98 final int N = ev.getHistorySize();
99 for (int i=0; i<N; i++) {
100 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
101 ev.getHistoricalEventTime(i));
102 }
103 addPoint(ev.getX(), ev.getY(), time);
104 }
105
106 private void addPoint(float x, float y, long time) {
107 int drop = -1;
108 int i;
109 if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
110 final long[] pastTime = mPastTime;
111 for (i=0; i<NUM_PAST; i++) {
112 if (pastTime[i] == 0) {
113 break;
114 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
115 if (localLOGV) Log.v(TAG, "Dropping past too old at "
116 + i + " time=" + pastTime[i]);
117 drop = i;
118 }
119 }
120 if (localLOGV) Log.v(TAG, "Add index: " + i);
121 if (i == NUM_PAST && drop < 0) {
122 drop = 0;
123 }
124 if (drop == i) drop--;
125 final float[] pastX = mPastX;
126 final float[] pastY = mPastY;
127 if (drop >= 0) {
128 if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
129 final int start = drop+1;
130 final int count = NUM_PAST-drop-1;
131 System.arraycopy(pastX, start, pastX, 0, count);
132 System.arraycopy(pastY, start, pastY, 0, count);
133 System.arraycopy(pastTime, start, pastTime, 0, count);
134 i -= (drop+1);
135 }
136 pastX[i] = x;
137 pastY[i] = y;
138 pastTime[i] = time;
139 i++;
140 if (i < NUM_PAST) {
141 pastTime[i] = 0;
142 }
143 }
144
145 /**
146 * Compute the current velocity based on the points that have been
147 * collected. Only call this when you actually want to retrieve velocity
148 * information, as it is relatively expensive. You can then retrieve
149 * the velocity with {@link #getXVelocity()} and
150 * {@link #getYVelocity()}.
151 *
152 * @param units The units you would like the velocity in. A value of 1
153 * provides pixels per millisecond, 1000 provides pixels per second, etc.
154 */
155 public void computeCurrentVelocity(int units) {
156 final float[] pastX = mPastX;
157 final float[] pastY = mPastY;
158 final long[] pastTime = mPastTime;
159
160 // Kind-of stupid.
161 final float oldestX = pastX[0];
162 final float oldestY = pastY[0];
163 final long oldestTime = pastTime[0];
164 float accumX = 0;
165 float accumY = 0;
166 int N=0;
167 while (N < NUM_PAST) {
168 if (pastTime[N] == 0) {
169 break;
170 }
171 N++;
172 }
173 // Skip the last received event, since it is probably pretty noisy.
174 if (N > 3) N--;
175
176 for (int i=1; i < N; i++) {
177 final int dur = (int)(pastTime[i] - oldestTime);
178 if (dur == 0) continue;
179 float dist = pastX[i] - oldestX;
180 float vel = (dist/dur) * units; // pixels/frame.
181 if (accumX == 0) accumX = vel;
182 else accumX = (accumX + vel) * .5f;
183
184 dist = pastY[i] - oldestY;
185 vel = (dist/dur) * units; // pixels/frame.
186 if (accumY == 0) accumY = vel;
187 else accumY = (accumY + vel) * .5f;
188 }
189 mXVelocity = accumX;
190 mYVelocity = accumY;
191
192 if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
193 + mXVelocity + " N=" + N);
194 }
195
196 /**
197 * Retrieve the last computed X velocity. You must first call
198 * {@link #computeCurrentVelocity(int)} before calling this function.
199 *
200 * @return The previously computed X velocity.
201 */
202 public float getXVelocity() {
203 return mXVelocity;
204 }
205
206 /**
207 * Retrieve the last computed Y velocity. You must first call
208 * {@link #computeCurrentVelocity(int)} before calling this function.
209 *
210 * @return The previously computed Y velocity.
211 */
212 public float getYVelocity() {
213 return mYVelocity;
214 }
215}