package com.android.launcher3.allapps; import android.content.Context; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; /** * One dimensional scroll gesture detector for all apps container pull up interaction. */ public class VerticalPullDetector { private static final String TAG = "ScrollGesture"; private static final boolean DBG = false; private float mTouchSlop; private boolean mAllAppsVisible; private boolean mAllAppsScrollAtTop; /** * The minimum release velocity in pixels per millisecond that triggers fling.. */ private static final float RELEASE_VELOCITY_PX_MS = 1.7f; /** * The time constant used to calculate dampening in the low-pass filter of scroll velocity. * Cutoff frequency is set at 10 Hz. */ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10); /* Scroll state, this is set to true during dragging and animation. */ boolean mScrolling; float mDownY; float mDownMillis; float mLastY; float mLastMillis; float mVelocity; float mLastDisplacement; float mDisplacement; /* scroll started during previous animation */ boolean mSubtractSlop = true; /* Client of this gesture detector can register a callback. */ Listener mListener; public void setListener(Listener l) { mListener = l; } interface Listener{ /** * @param start when should */ void onScrollStart(boolean start); boolean onScroll(float displacement, float velocity); void onScrollEnd(float velocity, boolean fling); } public VerticalPullDetector(Context context) { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } public void setAllAppsState(boolean allAppsVisible, boolean scrollAtTop) { mAllAppsVisible = allAppsVisible; mAllAppsScrollAtTop = scrollAtTop; } private boolean shouldScrollStart() { if (mAllAppsVisible && mDisplacement > mTouchSlop && mAllAppsScrollAtTop) { return true; } if (!mAllAppsVisible && mDisplacement < -mTouchSlop) { return true; } return false; } public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDownMillis = ev.getDownTime(); mDownY = ev.getY(); mLastDisplacement = 0; mVelocity = 0; if (mScrolling) { reportScrollStart(true /* recatch */); } break; case MotionEvent.ACTION_MOVE: mDisplacement = computeDisplacement(ev); mVelocity = computeVelocity(ev, mVelocity); if (!mScrolling && shouldScrollStart()) { mScrolling = true; reportScrollStart(false /* recatch */); } if (mScrolling && mListener != null) { reportScroll(); } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // These are synthetic events and there is no need to update internal values. if (mScrolling && mListener != null) { reportScrollEnd(); } break; default: //TODO: add multi finger tracking by tracking active pointer. break; } // Do house keeping. mLastDisplacement = mDisplacement; mLastY = ev.getY(); mLastMillis = ev.getEventTime(); return true; } public void finishedScrolling() { mScrolling = false; } private boolean reportScrollStart(boolean recatch) { mListener.onScrollStart(!recatch); if (DBG) { Log.d(TAG, "onScrollStart recatch:" + recatch); } return true; } private boolean reportScroll() { float delta = mDisplacement - mLastDisplacement; if (delta != 0) { if (DBG) { Log.d(TAG, String.format("onScroll disp=%.1f, velocity=%.1f", mDisplacement, mVelocity)); } return mListener.onScroll(mDisplacement - (mSubtractSlop? mTouchSlop : 0), mVelocity); } return true; } private void reportScrollEnd() { if (DBG) { Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f", mDisplacement, mVelocity)); } mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS); } /** * Computes the damped velocity using the two motion events and the previous velocity. */ private float computeVelocity(MotionEvent to, float previousVelocity) { float delta = computeDelta(to); float deltaTimeMillis = to.getEventTime() - mLastMillis; float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0; if (Math.abs(previousVelocity) < 0.001f) { return velocity; } float alpha = computeDampeningFactor(deltaTimeMillis); return interpolate(previousVelocity, velocity, alpha); } private float computeDisplacement(MotionEvent to) { return to.getY() - mDownY; } private float computeDelta(MotionEvent to) { return to.getY() - mLastY; } /** * Returns a time-dependent dampening factor using delta time. */ private static float computeDampeningFactor(float deltaTime) { return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime); } /** * Returns the linear interpolation between two values */ private static float interpolate(float from, float to, float alpha) { return (1.0f - alpha) * from + alpha * to; } }