Home | History | Annotate | Download | only in allapps
      1 package com.android.launcher3.allapps;
      2 
      3 import android.content.Context;
      4 import android.util.Log;
      5 import android.view.MotionEvent;
      6 import android.view.ViewConfiguration;
      7 
      8 /**
      9  * One dimensional scroll gesture detector for all apps container pull up interaction.
     10  * Client (e.g., AllAppsTransitionController) of this class can register a listener.
     11  * <p/>
     12  * Features that this gesture detector can support.
     13  */
     14 public class VerticalPullDetector {
     15 
     16     private static final boolean DBG = false;
     17     private static final String TAG = "VerticalPullDetector";
     18 
     19     private float mTouchSlop;
     20 
     21     private int mScrollConditions;
     22     public static final int DIRECTION_UP = 1 << 0;
     23     public static final int DIRECTION_DOWN = 1 << 1;
     24     public static final int DIRECTION_BOTH = DIRECTION_DOWN | DIRECTION_UP;
     25 
     26     /**
     27      * The minimum release velocity in pixels per millisecond that triggers fling..
     28      */
     29     public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
     30 
     31     /**
     32      * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
     33      * Cutoff frequency is set at 10 Hz.
     34      */
     35     public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
     36 
     37     /* Scroll state, this is set to true during dragging and animation. */
     38     private ScrollState mState = ScrollState.IDLE;
     39 
     40     enum ScrollState {
     41         IDLE,
     42         DRAGGING,      // onDragStart, onDrag
     43         SETTLING       // onDragEnd
     44     }
     45 
     46     ;
     47 
     48     //------------------- ScrollState transition diagram -----------------------------------
     49     //
     50     // IDLE ->      (mDisplacement > mTouchSlop) -> DRAGGING
     51     // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
     52     // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
     53     // SETTLING -> (View settled) -> IDLE
     54 
     55     private void setState(ScrollState newState) {
     56         if (DBG) {
     57             Log.d(TAG, "setState:" + mState + "->" + newState);
     58         }
     59         // onDragStart and onDragEnd is reported ONLY on state transition
     60         if (newState == ScrollState.DRAGGING) {
     61             initializeDragging();
     62             if (mState == ScrollState.IDLE) {
     63                 reportDragStart(false /* recatch */);
     64             } else if (mState == ScrollState.SETTLING) {
     65                 reportDragStart(true /* recatch */);
     66             }
     67         }
     68         if (newState == ScrollState.SETTLING) {
     69             reportDragEnd();
     70         }
     71 
     72         mState = newState;
     73     }
     74 
     75     public boolean isDraggingOrSettling() {
     76         return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     77     }
     78 
     79     /**
     80      * There's no touch and there's no animation.
     81      */
     82     public boolean isIdleState() {
     83         return mState == ScrollState.IDLE;
     84     }
     85 
     86     public boolean isSettlingState() {
     87         return mState == ScrollState.SETTLING;
     88     }
     89 
     90     public boolean isDraggingState() {
     91         return mState == ScrollState.DRAGGING;
     92     }
     93 
     94     private float mDownX;
     95     private float mDownY;
     96 
     97     private float mLastY;
     98     private long mCurrentMillis;
     99 
    100     private float mVelocity;
    101     private float mLastDisplacement;
    102     private float mDisplacementY;
    103     private float mDisplacementX;
    104 
    105     private float mSubtractDisplacement;
    106     private boolean mIgnoreSlopWhenSettling;
    107 
    108     /* Client of this gesture detector can register a callback. */
    109     Listener mListener;
    110 
    111     public void setListener(Listener l) {
    112         mListener = l;
    113     }
    114 
    115     interface Listener {
    116         void onDragStart(boolean start);
    117 
    118         boolean onDrag(float displacement, float velocity);
    119 
    120         void onDragEnd(float velocity, boolean fling);
    121     }
    122 
    123     public VerticalPullDetector(Context context) {
    124         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    125     }
    126 
    127     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
    128         mScrollConditions = scrollDirectionFlags;
    129         mIgnoreSlopWhenSettling = ignoreSlop;
    130     }
    131 
    132     private boolean shouldScrollStart() {
    133         // reject cases where the slop condition is not met.
    134         if (Math.abs(mDisplacementY) < mTouchSlop) {
    135             return false;
    136         }
    137 
    138         // reject cases where the angle condition is not met.
    139         float deltaY = Math.abs(mDisplacementY);
    140         float deltaX = Math.max(Math.abs(mDisplacementX), 1);
    141         if (deltaX > deltaY) {
    142             return false;
    143         }
    144         // Check if the client is interested in scroll in current direction.
    145         if (((mScrollConditions & DIRECTION_DOWN) > 0 && mDisplacementY > 0) ||
    146                 ((mScrollConditions & DIRECTION_UP) > 0 && mDisplacementY < 0)) {
    147             return true;
    148         }
    149         return false;
    150     }
    151 
    152     public boolean onTouchEvent(MotionEvent ev) {
    153         switch (ev.getAction()) {
    154             case MotionEvent.ACTION_DOWN:
    155                 mDownX = ev.getX();
    156                 mDownY = ev.getY();
    157                 mLastDisplacement = 0;
    158                 mDisplacementY = 0;
    159                 mVelocity = 0;
    160 
    161                 if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
    162                     setState(ScrollState.DRAGGING);
    163                 }
    164                 break;
    165             case MotionEvent.ACTION_MOVE:
    166                 mDisplacementX = ev.getX() - mDownX;
    167                 mDisplacementY = ev.getY() - mDownY;
    168                 computeVelocity(ev);
    169 
    170                 // handle state and listener calls.
    171                 if (mState != ScrollState.DRAGGING && shouldScrollStart()) {
    172                     setState(ScrollState.DRAGGING);
    173                 }
    174                 if (mState == ScrollState.DRAGGING) {
    175                     reportDragging();
    176                 }
    177                 break;
    178             case MotionEvent.ACTION_CANCEL:
    179             case MotionEvent.ACTION_UP:
    180                 // These are synthetic events and there is no need to update internal values.
    181                 if (mState == ScrollState.DRAGGING) {
    182                     setState(ScrollState.SETTLING);
    183                 }
    184                 break;
    185             default:
    186                 //TODO: add multi finger tracking by tracking active pointer.
    187                 break;
    188         }
    189         // Do house keeping.
    190         mLastDisplacement = mDisplacementY;
    191         mLastY = ev.getY();
    192         return true;
    193     }
    194 
    195     public void finishedScrolling() {
    196         setState(ScrollState.IDLE);
    197     }
    198 
    199     private boolean reportDragStart(boolean recatch) {
    200         mListener.onDragStart(!recatch);
    201         if (DBG) {
    202             Log.d(TAG, "onDragStart recatch:" + recatch);
    203         }
    204         return true;
    205     }
    206 
    207     private void initializeDragging() {
    208         if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
    209             mSubtractDisplacement = 0;
    210         }
    211         if (mDisplacementY > 0) {
    212             mSubtractDisplacement = mTouchSlop;
    213         } else {
    214             mSubtractDisplacement = -mTouchSlop;
    215         }
    216     }
    217 
    218     private boolean reportDragging() {
    219         float delta = mDisplacementY - mLastDisplacement;
    220         if (delta != 0) {
    221             if (DBG) {
    222                 Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
    223                         mDisplacementY, mVelocity));
    224             }
    225 
    226             return mListener.onDrag(mDisplacementY - mSubtractDisplacement, mVelocity);
    227         }
    228         return true;
    229     }
    230 
    231     private void reportDragEnd() {
    232         if (DBG) {
    233             Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
    234                     mDisplacementY, mVelocity));
    235         }
    236         mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
    237 
    238     }
    239 
    240     /**
    241      * Computes the damped velocity using the two motion events and the previous velocity.
    242      */
    243     private float computeVelocity(MotionEvent to) {
    244         return computeVelocity(to.getY() - mLastY, to.getEventTime());
    245     }
    246 
    247     public float computeVelocity(float delta, long currentMillis) {
    248         long previousMillis = mCurrentMillis;
    249         mCurrentMillis = currentMillis;
    250 
    251         float deltaTimeMillis = mCurrentMillis - previousMillis;
    252         float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
    253         if (Math.abs(mVelocity) < 0.001f) {
    254             mVelocity = velocity;
    255         } else {
    256             float alpha = computeDampeningFactor(deltaTimeMillis);
    257             mVelocity = interpolate(mVelocity, velocity, alpha);
    258         }
    259         return mVelocity;
    260     }
    261 
    262     /**
    263      * Returns a time-dependent dampening factor using delta time.
    264      */
    265     private static float computeDampeningFactor(float deltaTime) {
    266         return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
    267     }
    268 
    269     /**
    270      * Returns the linear interpolation between two values
    271      */
    272     private static float interpolate(float from, float to, float alpha) {
    273         return (1.0f - alpha) * from + alpha * to;
    274     }
    275 }
    276