• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
setState(ScrollState newState)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 
isDraggingOrSettling()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      */
isIdleState()82     public boolean isIdleState() {
83         return mState == ScrollState.IDLE;
84     }
85 
isSettlingState()86     public boolean isSettlingState() {
87         return mState == ScrollState.SETTLING;
88     }
89 
isDraggingState()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 
setListener(Listener l)111     public void setListener(Listener l) {
112         mListener = l;
113     }
114 
115     interface Listener {
onDragStart(boolean start)116         void onDragStart(boolean start);
117 
onDrag(float displacement, float velocity)118         boolean onDrag(float displacement, float velocity);
119 
onDragEnd(float velocity, boolean fling)120         void onDragEnd(float velocity, boolean fling);
121     }
122 
VerticalPullDetector(Context context)123     public VerticalPullDetector(Context context) {
124         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
125     }
126 
setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop)127     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
128         mScrollConditions = scrollDirectionFlags;
129         mIgnoreSlopWhenSettling = ignoreSlop;
130     }
131 
shouldScrollStart()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 
onTouchEvent(MotionEvent ev)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 
finishedScrolling()195     public void finishedScrolling() {
196         setState(ScrollState.IDLE);
197     }
198 
reportDragStart(boolean recatch)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 
initializeDragging()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 
reportDragging()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 
reportDragEnd()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      */
computeVelocity(MotionEvent to)243     private float computeVelocity(MotionEvent to) {
244         return computeVelocity(to.getY() - mLastY, to.getEventTime());
245     }
246 
computeVelocity(float delta, long currentMillis)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      */
computeDampeningFactor(float deltaTime)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      */
interpolate(float from, float to, float alpha)272     private static float interpolate(float from, float to, float alpha) {
273         return (1.0f - alpha) * from + alpha * to;
274     }
275 }
276