| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.view; |
| |
| import android.util.Config; |
| import android.util.Log; |
| import android.util.Poolable; |
| import android.util.Pool; |
| import android.util.Pools; |
| import android.util.PoolableManager; |
| |
| /** |
| * Helper for tracking the velocity of touch events, for implementing |
| * flinging and other such gestures. Use {@link #obtain} to retrieve a |
| * new instance of the class when you are going to begin tracking, put |
| * the motion events you receive into it with {@link #addMovement(MotionEvent)}, |
| * and when you want to determine the velocity call |
| * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()} |
| * and {@link #getXVelocity()}. |
| */ |
| public final class VelocityTracker implements Poolable<VelocityTracker> { |
| static final String TAG = "VelocityTracker"; |
| static final boolean DEBUG = false; |
| static final boolean localLOGV = DEBUG || Config.LOGV; |
| |
| static final int NUM_PAST = 10; |
| static final int LONGEST_PAST_TIME = 200; |
| |
| static final VelocityTracker[] mPool = new VelocityTracker[1]; |
| private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( |
| Pools.finitePool(new PoolableManager<VelocityTracker>() { |
| public VelocityTracker newInstance() { |
| return new VelocityTracker(); |
| } |
| |
| public void onAcquired(VelocityTracker element) { |
| element.clear(); |
| } |
| |
| public void onReleased(VelocityTracker element) { |
| } |
| }, 2)); |
| |
| final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; |
| final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; |
| final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; |
| |
| float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; |
| float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; |
| int mLastTouch; |
| |
| private VelocityTracker mNext; |
| |
| /** |
| * Retrieve a new VelocityTracker object to watch the velocity of a |
| * motion. Be sure to call {@link #recycle} when done. You should |
| * generally only maintain an active object while tracking a movement, |
| * so that the VelocityTracker can be re-used elsewhere. |
| * |
| * @return Returns a new VelocityTracker. |
| */ |
| static public VelocityTracker obtain() { |
| return sPool.acquire(); |
| } |
| |
| /** |
| * Return a VelocityTracker object back to be re-used by others. You must |
| * not touch the object after calling this function. |
| */ |
| public void recycle() { |
| sPool.release(this); |
| } |
| |
| /** |
| * @hide |
| */ |
| public void setNextPoolable(VelocityTracker element) { |
| mNext = element; |
| } |
| |
| /** |
| * @hide |
| */ |
| public VelocityTracker getNextPoolable() { |
| return mNext; |
| } |
| |
| private VelocityTracker() { |
| } |
| |
| /** |
| * Reset the velocity tracker back to its initial state. |
| */ |
| public void clear() { |
| final long[][] pastTime = mPastTime; |
| for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) { |
| for (int i = 0; i < NUM_PAST; i++) { |
| pastTime[p][i] = 0; |
| } |
| } |
| } |
| |
| /** |
| * Add a user's movement to the tracker. You should call this for the |
| * initial {@link MotionEvent#ACTION_DOWN}, the following |
| * {@link MotionEvent#ACTION_MOVE} events that you receive, and the |
| * final {@link MotionEvent#ACTION_UP}. You can, however, call this |
| * for whichever events you desire. |
| * |
| * @param ev The MotionEvent you received and would like to track. |
| */ |
| public void addMovement(MotionEvent ev) { |
| final int N = ev.getHistorySize(); |
| final int pointerCount = ev.getPointerCount(); |
| int touchIndex = (mLastTouch + 1) % NUM_PAST; |
| for (int i=0; i<N; i++) { |
| for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { |
| mPastTime[id][touchIndex] = 0; |
| } |
| for (int p = 0; p < pointerCount; p++) { |
| int id = ev.getPointerId(p); |
| mPastX[id][touchIndex] = ev.getHistoricalX(p, i); |
| mPastY[id][touchIndex] = ev.getHistoricalY(p, i); |
| mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i); |
| } |
| |
| touchIndex = (touchIndex + 1) % NUM_PAST; |
| } |
| |
| // During calculation any pointer values with a time of 0 are treated |
| // as a break in input. Initialize all to 0 for each new touch index. |
| for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { |
| mPastTime[id][touchIndex] = 0; |
| } |
| final long time = ev.getEventTime(); |
| for (int p = 0; p < pointerCount; p++) { |
| int id = ev.getPointerId(p); |
| mPastX[id][touchIndex] = ev.getX(p); |
| mPastY[id][touchIndex] = ev.getY(p); |
| mPastTime[id][touchIndex] = time; |
| } |
| |
| mLastTouch = touchIndex; |
| } |
| |
| /** |
| * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum |
| * velocity of Float.MAX_VALUE. |
| * |
| * @see #computeCurrentVelocity(int, float) |
| */ |
| public void computeCurrentVelocity(int units) { |
| computeCurrentVelocity(units, Float.MAX_VALUE); |
| } |
| |
| /** |
| * Compute the current velocity based on the points that have been |
| * collected. Only call this when you actually want to retrieve velocity |
| * information, as it is relatively expensive. You can then retrieve |
| * the velocity with {@link #getXVelocity()} and |
| * {@link #getYVelocity()}. |
| * |
| * @param units The units you would like the velocity in. A value of 1 |
| * provides pixels per millisecond, 1000 provides pixels per second, etc. |
| * @param maxVelocity The maximum velocity that can be computed by this method. |
| * This value must be declared in the same unit as the units parameter. This value |
| * must be positive. |
| */ |
| public void computeCurrentVelocity(int units, float maxVelocity) { |
| for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) { |
| final float[] pastX = mPastX[pos]; |
| final float[] pastY = mPastY[pos]; |
| final long[] pastTime = mPastTime[pos]; |
| final int lastTouch = mLastTouch; |
| |
| // find oldest acceptable time |
| int oldestTouch = lastTouch; |
| if (pastTime[lastTouch] > 0) { // cleared ? |
| final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; |
| int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; |
| while (pastTime[nextOldestTouch] >= acceptableTime && |
| nextOldestTouch != lastTouch) { |
| oldestTouch = nextOldestTouch; |
| nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; |
| } |
| } |
| |
| // Kind-of stupid. |
| final float oldestX = pastX[oldestTouch]; |
| final float oldestY = pastY[oldestTouch]; |
| final long oldestTime = pastTime[oldestTouch]; |
| float accumX = 0; |
| float accumY = 0; |
| float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; |
| // Skip the last received event, since it is probably pretty noisy. |
| if (N > 3) N--; |
| |
| for (int i=1; i < N; i++) { |
| final int j = (oldestTouch + i) % NUM_PAST; |
| final int dur = (int)(pastTime[j] - oldestTime); |
| if (dur == 0) continue; |
| float dist = pastX[j] - oldestX; |
| float vel = (dist/dur) * units; // pixels/frame. |
| accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; |
| |
| dist = pastY[j] - oldestY; |
| vel = (dist/dur) * units; // pixels/frame. |
| accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; |
| } |
| |
| mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity) |
| : Math.min(accumX, maxVelocity); |
| mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity) |
| : Math.min(accumY, maxVelocity); |
| |
| if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" |
| + mXVelocity + " N=" + N); |
| } |
| } |
| |
| /** |
| * Retrieve the last computed X velocity. You must first call |
| * {@link #computeCurrentVelocity(int)} before calling this function. |
| * |
| * @return The previously computed X velocity. |
| */ |
| public float getXVelocity() { |
| return mXVelocity[0]; |
| } |
| |
| /** |
| * Retrieve the last computed Y velocity. You must first call |
| * {@link #computeCurrentVelocity(int)} before calling this function. |
| * |
| * @return The previously computed Y velocity. |
| */ |
| public float getYVelocity() { |
| return mYVelocity[0]; |
| } |
| |
| /** |
| * Retrieve the last computed X velocity. You must first call |
| * {@link #computeCurrentVelocity(int)} before calling this function. |
| * |
| * @param id Which pointer's velocity to return. |
| * @return The previously computed X velocity. |
| * |
| * @hide Pending API approval |
| */ |
| public float getXVelocity(int id) { |
| return mXVelocity[id]; |
| } |
| |
| /** |
| * Retrieve the last computed Y velocity. You must first call |
| * {@link #computeCurrentVelocity(int)} before calling this function. |
| * |
| * @param id Which pointer's velocity to return. |
| * @return The previously computed Y velocity. |
| * |
| * @hide Pending API approval |
| */ |
| public float getYVelocity(int id) { |
| return mYVelocity[id]; |
| } |
| } |