Home | History | Annotate | Download | only in view
      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 import android.util.Poolable;
     22 import android.util.Pool;
     23 import android.util.Pools;
     24 import 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  */
     35 public final class VelocityTracker implements Poolable<VelocityTracker> {
     36     private static final String TAG = "VelocityTracker";
     37     private static final boolean DEBUG = false;
     38     private static final boolean localLOGV = DEBUG || Config.LOGV;
     39 
     40     private static final int NUM_PAST = 10;
     41     private static final int MAX_AGE_MILLISECONDS = 200;
     42 
     43     private static final int POINTER_POOL_CAPACITY = 20;
     44 
     45     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
     46             Pools.finitePool(new PoolableManager<VelocityTracker>() {
     47                 public VelocityTracker newInstance() {
     48                     return new VelocityTracker();
     49                 }
     50 
     51                 public void onAcquired(VelocityTracker element) {
     52                 }
     53 
     54                 public void onReleased(VelocityTracker element) {
     55                     element.clear();
     56                 }
     57             }, 2));
     58 
     59     private static Pointer sRecycledPointerListHead;
     60     private static int sRecycledPointerCount;
     61 
     62     private static final class Pointer {
     63         public Pointer next;
     64 
     65         public int id;
     66         public float xVelocity;
     67         public float yVelocity;
     68 
     69         public final float[] pastX = new float[NUM_PAST];
     70         public final float[] pastY = new float[NUM_PAST];
     71         public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
     72 
     73         public int generation;
     74     }
     75 
     76     private Pointer mPointerListHead; // sorted by id in increasing order
     77     private int mLastTouchIndex;
     78     private int mGeneration;
     79 
     80     private VelocityTracker mNext;
     81 
     82     /**
     83      * Retrieve a new VelocityTracker object to watch the velocity of a
     84      * motion.  Be sure to call {@link #recycle} when done.  You should
     85      * generally only maintain an active object while tracking a movement,
     86      * so that the VelocityTracker can be re-used elsewhere.
     87      *
     88      * @return Returns a new VelocityTracker.
     89      */
     90     static public VelocityTracker obtain() {
     91         return sPool.acquire();
     92     }
     93 
     94     /**
     95      * Return a VelocityTracker object back to be re-used by others.  You must
     96      * not touch the object after calling this function.
     97      */
     98     public void recycle() {
     99         sPool.release(this);
    100     }
    101 
    102     /**
    103      * @hide
    104      */
    105     public void setNextPoolable(VelocityTracker element) {
    106         mNext = element;
    107     }
    108 
    109     /**
    110      * @hide
    111      */
    112     public VelocityTracker getNextPoolable() {
    113         return mNext;
    114     }
    115 
    116     private VelocityTracker() {
    117         clear();
    118     }
    119 
    120     /**
    121      * Reset the velocity tracker back to its initial state.
    122      */
    123     public void clear() {
    124         releasePointerList(mPointerListHead);
    125 
    126         mPointerListHead = null;
    127         mLastTouchIndex = 0;
    128     }
    129 
    130     /**
    131      * Add a user's movement to the tracker.  You should call this for the
    132      * initial {@link MotionEvent#ACTION_DOWN}, the following
    133      * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
    134      * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
    135      * for whichever events you desire.
    136      *
    137      * @param ev The MotionEvent you received and would like to track.
    138      */
    139     public void addMovement(MotionEvent ev) {
    140         final int historySize = ev.getHistorySize();
    141         final int pointerCount = ev.getPointerCount();
    142         final int lastTouchIndex = mLastTouchIndex;
    143         final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
    144         final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
    145         final int generation = mGeneration++;
    146 
    147         mLastTouchIndex = finalTouchIndex;
    148 
    149         // Update pointer data.
    150         Pointer previousPointer = null;
    151         for (int i = 0; i < pointerCount; i++){
    152             final int pointerId = ev.getPointerId(i);
    153 
    154             // Find the pointer data for this pointer id.
    155             // This loop is optimized for the common case where pointer ids in the event
    156             // are in sorted order.  However, we check for this case explicitly and
    157             // perform a full linear scan from the start if needed.
    158             Pointer nextPointer;
    159             if (previousPointer == null || pointerId < previousPointer.id) {
    160                 previousPointer = null;
    161                 nextPointer = mPointerListHead;
    162             } else {
    163                 nextPointer = previousPointer.next;
    164             }
    165 
    166             final Pointer pointer;
    167             for (;;) {
    168                 if (nextPointer != null) {
    169                     final int nextPointerId = nextPointer.id;
    170                     if (nextPointerId == pointerId) {
    171                         pointer = nextPointer;
    172                         break;
    173                     }
    174                     if (nextPointerId < pointerId) {
    175                         nextPointer = nextPointer.next;
    176                         continue;
    177                     }
    178                 }
    179 
    180                 // Pointer went down.  Add it to the list.
    181                 // Write a sentinel at the end of the pastTime trace so we will be able to
    182                 // tell when the trace started.
    183                 pointer = obtainPointer();
    184                 pointer.id = pointerId;
    185                 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
    186                 pointer.next = nextPointer;
    187                 if (previousPointer == null) {
    188                     mPointerListHead = pointer;
    189                 } else {
    190                     previousPointer.next = pointer;
    191                 }
    192                 break;
    193             }
    194 
    195             pointer.generation = generation;
    196             previousPointer = pointer;
    197 
    198             final float[] pastX = pointer.pastX;
    199             final float[] pastY = pointer.pastY;
    200             final long[] pastTime = pointer.pastTime;
    201 
    202             for (int j = 0; j < historySize; j++) {
    203                 final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
    204                 pastX[touchIndex] = ev.getHistoricalX(i, j);
    205                 pastY[touchIndex] = ev.getHistoricalY(i, j);
    206                 pastTime[touchIndex] = ev.getHistoricalEventTime(j);
    207             }
    208             pastX[finalTouchIndex] = ev.getX(i);
    209             pastY[finalTouchIndex] = ev.getY(i);
    210             pastTime[finalTouchIndex] = ev.getEventTime();
    211         }
    212 
    213         // Find removed pointers.
    214         previousPointer = null;
    215         for (Pointer pointer = mPointerListHead; pointer != null; ) {
    216             final Pointer nextPointer = pointer.next;
    217             if (pointer.generation != generation) {
    218                 // Pointer went up.  Remove it from the list.
    219                 if (previousPointer == null) {
    220                     mPointerListHead = nextPointer;
    221                 } else {
    222                     previousPointer.next = nextPointer;
    223                 }
    224                 releasePointer(pointer);
    225             } else {
    226                 previousPointer = pointer;
    227             }
    228             pointer = nextPointer;
    229         }
    230     }
    231 
    232     /**
    233      * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
    234      * velocity of Float.MAX_VALUE.
    235      *
    236      * @see #computeCurrentVelocity(int, float)
    237      */
    238     public void computeCurrentVelocity(int units) {
    239         computeCurrentVelocity(units, Float.MAX_VALUE);
    240     }
    241 
    242     /**
    243      * Compute the current velocity based on the points that have been
    244      * collected.  Only call this when you actually want to retrieve velocity
    245      * information, as it is relatively expensive.  You can then retrieve
    246      * the velocity with {@link #getXVelocity()} and
    247      * {@link #getYVelocity()}.
    248      *
    249      * @param units The units you would like the velocity in.  A value of 1
    250      * provides pixels per millisecond, 1000 provides pixels per second, etc.
    251      * @param maxVelocity The maximum velocity that can be computed by this method.
    252      * This value must be declared in the same unit as the units parameter. This value
    253      * must be positive.
    254      */
    255     public void computeCurrentVelocity(int units, float maxVelocity) {
    256         final int lastTouchIndex = mLastTouchIndex;
    257 
    258         for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
    259             final long[] pastTime = pointer.pastTime;
    260 
    261             // Search backwards in time for oldest acceptable time.
    262             // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
    263             int oldestTouchIndex = lastTouchIndex;
    264             int numTouches = 1;
    265             final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
    266             while (numTouches < NUM_PAST) {
    267                 final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
    268                 final long nextOldestTime = pastTime[nextOldestTouchIndex];
    269                 if (nextOldestTime < minTime) { // also handles end of trace sentinel
    270                     break;
    271                 }
    272                 oldestTouchIndex = nextOldestTouchIndex;
    273                 numTouches += 1;
    274             }
    275 
    276             // If we have a lot of samples, skip the last received sample since it is
    277             // probably pretty noisy compared to the sum of all of the traces already acquired.
    278             if (numTouches > 3) {
    279                 numTouches -= 1;
    280             }
    281 
    282             // Kind-of stupid.
    283             final float[] pastX = pointer.pastX;
    284             final float[] pastY = pointer.pastY;
    285 
    286             final float oldestX = pastX[oldestTouchIndex];
    287             final float oldestY = pastY[oldestTouchIndex];
    288             final long oldestTime = pastTime[oldestTouchIndex];
    289 
    290             float accumX = 0;
    291             float accumY = 0;
    292 
    293             for (int i = 1; i < numTouches; i++) {
    294                 final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
    295                 final int duration = (int)(pastTime[touchIndex] - oldestTime);
    296 
    297                 if (duration == 0) continue;
    298 
    299                 float delta = pastX[touchIndex] - oldestX;
    300                 float velocity = (delta / duration) * units; // pixels/frame.
    301                 accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
    302 
    303                 delta = pastY[touchIndex] - oldestY;
    304                 velocity = (delta / duration) * units; // pixels/frame.
    305                 accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
    306             }
    307 
    308             if (accumX < -maxVelocity) {
    309                 accumX = - maxVelocity;
    310             } else if (accumX > maxVelocity) {
    311                 accumX = maxVelocity;
    312             }
    313 
    314             if (accumY < -maxVelocity) {
    315                 accumY = - maxVelocity;
    316             } else if (accumY > maxVelocity) {
    317                 accumY = maxVelocity;
    318             }
    319 
    320             pointer.xVelocity = accumX;
    321             pointer.yVelocity = accumY;
    322 
    323             if (localLOGV) {
    324                 Log.v(TAG, "Pointer " + pointer.id
    325                     + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
    326             }
    327         }
    328     }
    329 
    330     /**
    331      * Retrieve the last computed X velocity.  You must first call
    332      * {@link #computeCurrentVelocity(int)} before calling this function.
    333      *
    334      * @return The previously computed X velocity.
    335      */
    336     public float getXVelocity() {
    337         Pointer pointer = getPointer(0);
    338         return pointer != null ? pointer.xVelocity : 0;
    339     }
    340 
    341     /**
    342      * Retrieve the last computed Y velocity.  You must first call
    343      * {@link #computeCurrentVelocity(int)} before calling this function.
    344      *
    345      * @return The previously computed Y velocity.
    346      */
    347     public float getYVelocity() {
    348         Pointer pointer = getPointer(0);
    349         return pointer != null ? pointer.yVelocity : 0;
    350     }
    351 
    352     /**
    353      * Retrieve the last computed X velocity.  You must first call
    354      * {@link #computeCurrentVelocity(int)} before calling this function.
    355      *
    356      * @param id Which pointer's velocity to return.
    357      * @return The previously computed X velocity.
    358      */
    359     public float getXVelocity(int id) {
    360         Pointer pointer = getPointer(id);
    361         return pointer != null ? pointer.xVelocity : 0;
    362     }
    363 
    364     /**
    365      * Retrieve the last computed Y velocity.  You must first call
    366      * {@link #computeCurrentVelocity(int)} before calling this function.
    367      *
    368      * @param id Which pointer's velocity to return.
    369      * @return The previously computed Y velocity.
    370      */
    371     public float getYVelocity(int id) {
    372         Pointer pointer = getPointer(id);
    373         return pointer != null ? pointer.yVelocity : 0;
    374     }
    375 
    376     private final Pointer getPointer(int id) {
    377         for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
    378             if (pointer.id == id) {
    379                 return pointer;
    380             }
    381         }
    382         return null;
    383     }
    384 
    385     private static final Pointer obtainPointer() {
    386         synchronized (sPool) {
    387             if (sRecycledPointerCount != 0) {
    388                 Pointer element = sRecycledPointerListHead;
    389                 sRecycledPointerCount -= 1;
    390                 sRecycledPointerListHead = element.next;
    391                 element.next = null;
    392                 return element;
    393             }
    394         }
    395         return new Pointer();
    396     }
    397 
    398     private static final void releasePointer(Pointer pointer) {
    399         synchronized (sPool) {
    400             if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
    401                 pointer.next = sRecycledPointerListHead;
    402                 sRecycledPointerCount += 1;
    403                 sRecycledPointerListHead = pointer;
    404             }
    405         }
    406     }
    407 
    408     private static final void releasePointerList(Pointer pointer) {
    409         if (pointer != null) {
    410             synchronized (sPool) {
    411                 int count = sRecycledPointerCount;
    412                 if (count >= POINTER_POOL_CAPACITY) {
    413                     return;
    414                 }
    415 
    416                 Pointer tail = pointer;
    417                 for (;;) {
    418                     count += 1;
    419                     if (count >= POINTER_POOL_CAPACITY) {
    420                         break;
    421                     }
    422 
    423                     Pointer next = tail.next;
    424                     if (next == null) {
    425                         break;
    426                     }
    427                     tail = next;
    428                 }
    429 
    430                 tail.next = sRecycledPointerListHead;
    431                 sRecycledPointerCount = count;
    432                 sRecycledPointerListHead = pointer;
    433             }
    434         }
    435     }
    436 }
    437