The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
| 17 | package android.view; |
| 18 | |
| 19 | import android.util.Config; |
| 20 | import android.util.Log; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 21 | import android.util.Poolable; |
| 22 | import android.util.Pool; |
Romain Guy | 10b4043 | 2009-04-02 09:03:39 -0700 | [diff] [blame] | 23 | import android.util.Pools; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 24 | import android.util.PoolableManager; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | */ |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 35 | public final class VelocityTracker implements Poolable<VelocityTracker> { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 36 | static final String TAG = "VelocityTracker"; |
| 37 | static final boolean DEBUG = false; |
| 38 | static final boolean localLOGV = DEBUG || Config.LOGV; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 39 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 40 | static final int NUM_PAST = 10; |
| 41 | static final int LONGEST_PAST_TIME = 200; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 42 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 43 | static final VelocityTracker[] mPool = new VelocityTracker[1]; |
Romain Guy | 10b4043 | 2009-04-02 09:03:39 -0700 | [diff] [blame] | 44 | private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( |
| 45 | Pools.finitePool(new PoolableManager<VelocityTracker>() { |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 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 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 58 | final float mPastX[] = new float[NUM_PAST]; |
| 59 | final float mPastY[] = new float[NUM_PAST]; |
| 60 | final long mPastTime[] = new long[NUM_PAST]; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 61 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 62 | float mYVelocity; |
| 63 | float mXVelocity; |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 64 | |
| 65 | private VelocityTracker mNext; |
| 66 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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. |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 72 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 73 | * @return Returns a new VelocityTracker. |
| 74 | */ |
| 75 | static public VelocityTracker obtain() { |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 76 | return sPool.acquire(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 77 | } |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 78 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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() { |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 84 | sPool.release(this); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 85 | } |
Romain Guy | d1b3dd0 | 2009-03-31 17:53:57 -0700 | [diff] [blame] | 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 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | * Compute the current velocity based on the points that have been |
| 171 | * collected. Only call this when you actually want to retrieve velocity |
| 172 | * information, as it is relatively expensive. You can then retrieve |
| 173 | * the velocity with {@link #getXVelocity()} and |
| 174 | * {@link #getYVelocity()}. |
| 175 | * |
| 176 | * @param units The units you would like the velocity in. A value of 1 |
| 177 | * provides pixels per millisecond, 1000 provides pixels per second, etc. |
| 178 | */ |
| 179 | public void computeCurrentVelocity(int units) { |
| 180 | final float[] pastX = mPastX; |
| 181 | final float[] pastY = mPastY; |
| 182 | final long[] pastTime = mPastTime; |
| 183 | |
| 184 | // Kind-of stupid. |
| 185 | final float oldestX = pastX[0]; |
| 186 | final float oldestY = pastY[0]; |
| 187 | final long oldestTime = pastTime[0]; |
| 188 | float accumX = 0; |
| 189 | float accumY = 0; |
| 190 | int N=0; |
| 191 | while (N < NUM_PAST) { |
| 192 | if (pastTime[N] == 0) { |
| 193 | break; |
| 194 | } |
| 195 | N++; |
| 196 | } |
| 197 | // Skip the last received event, since it is probably pretty noisy. |
| 198 | if (N > 3) N--; |
| 199 | |
| 200 | for (int i=1; i < N; i++) { |
| 201 | final int dur = (int)(pastTime[i] - oldestTime); |
| 202 | if (dur == 0) continue; |
| 203 | float dist = pastX[i] - oldestX; |
| 204 | float vel = (dist/dur) * units; // pixels/frame. |
| 205 | if (accumX == 0) accumX = vel; |
| 206 | else accumX = (accumX + vel) * .5f; |
| 207 | |
| 208 | dist = pastY[i] - oldestY; |
| 209 | vel = (dist/dur) * units; // pixels/frame. |
| 210 | if (accumY == 0) accumY = vel; |
| 211 | else accumY = (accumY + vel) * .5f; |
| 212 | } |
| 213 | mXVelocity = accumX; |
| 214 | mYVelocity = accumY; |
| 215 | |
| 216 | if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" |
| 217 | + mXVelocity + " N=" + N); |
| 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; |
| 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; |
| 238 | } |
| 239 | } |