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; |
| 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 | */ |
| 31 | public 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 | } |