Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2013 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 
     18 package com.android.dialer.widget;
     19 
     20 import android.content.Context;
     21 import android.support.v4.view.MotionEventCompat;
     22 import android.support.v4.view.VelocityTrackerCompat;
     23 import android.support.v4.view.ViewCompat;
     24 import android.view.MotionEvent;
     25 import android.view.VelocityTracker;
     26 import android.view.View;
     27 import android.view.ViewConfiguration;
     28 import android.view.ViewGroup;
     29 import android.widget.Scroller;
     30 
     31 import java.util.Arrays;
     32 
     33 /**
     34  * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
     35  * of useful operations and state tracking for allowing a user to drag and reposition
     36  * views within their parent ViewGroup.
     37  */
     38 public class ViewDragHelper {
     39     private static final String TAG = "ViewDragHelper";
     40 
     41     /**
     42      * A null/invalid pointer ID.
     43      */
     44     public static final int INVALID_POINTER = -1;
     45 
     46     /**
     47      * A view is not currently being dragged or animating as a result of a fling/snap.
     48      */
     49     public static final int STATE_IDLE = 0;
     50 
     51     /**
     52      * A view is currently being dragged. The position is currently changing as a result
     53      * of user input or simulated user input.
     54      */
     55     public static final int STATE_DRAGGING = 1;
     56 
     57     /**
     58      * A view is currently settling into place as a result of a fling or
     59      * predefined non-interactive motion.
     60      */
     61     public static final int STATE_SETTLING = 2;
     62 
     63     /**
     64      * Edge flag indicating that the left edge should be affected.
     65      */
     66     public static final int EDGE_LEFT = 1 << 0;
     67 
     68     /**
     69      * Edge flag indicating that the right edge should be affected.
     70      */
     71     public static final int EDGE_RIGHT = 1 << 1;
     72 
     73     /**
     74      * Edge flag indicating that the top edge should be affected.
     75      */
     76     public static final int EDGE_TOP = 1 << 2;
     77 
     78     /**
     79      * Edge flag indicating that the bottom edge should be affected.
     80      */
     81     public static final int EDGE_BOTTOM = 1 << 3;
     82 
     83     /**
     84      * Edge flag set indicating all edges should be affected.
     85      */
     86     public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
     87 
     88     /**
     89      * Indicates that a check should occur along the horizontal axis
     90      */
     91     public static final int DIRECTION_HORIZONTAL = 1 << 0;
     92 
     93     /**
     94      * Indicates that a check should occur along the vertical axis
     95      */
     96     public static final int DIRECTION_VERTICAL = 1 << 1;
     97 
     98     /**
     99      * Indicates that a check should occur along all axes
    100      */
    101     public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
    102 
    103     private static final int EDGE_SIZE = 20; // dp
    104 
    105     private static final int BASE_SETTLE_DURATION = 256; // ms
    106     private static final int MAX_SETTLE_DURATION = 600; // ms
    107 
    108     // Current drag state; idle, dragging or settling
    109     private int mDragState;
    110 
    111     // Distance to travel before a drag may begin
    112     private int mTouchSlop;
    113 
    114     // Last known position/pointer tracking
    115     private int mActivePointerId = INVALID_POINTER;
    116     private float[] mInitialMotionX;
    117     private float[] mInitialMotionY;
    118     private float[] mLastMotionX;
    119     private float[] mLastMotionY;
    120     private int[] mInitialEdgesTouched;
    121     private int[] mEdgeDragsInProgress;
    122     private int[] mEdgeDragsLocked;
    123     private int mPointersDown;
    124 
    125     private VelocityTracker mVelocityTracker;
    126     private float mMaxVelocity;
    127     private float mMinVelocity;
    128 
    129     private int mEdgeSize;
    130     private int mTrackingEdges;
    131 
    132     // We need to use a Scroller instead of an OverScroller (b/17700698) and as a result, we need
    133     // to keep track of the final scroll position ourselves in mFinalScrollY (b/17704016) whenever
    134     // we programmatically scroll or fling mScroller.
    135     private Scroller mScroller;
    136     private int mFinalScrollY;
    137 
    138     private final Callback mCallback;
    139 
    140     private View mCapturedView;
    141     private boolean mReleaseInProgress;
    142 
    143     private final ViewGroup mParentView;
    144 
    145     /**
    146      * A Callback is used as a communication channel with the ViewDragHelper back to the
    147      * parent view using it. <code>on*</code>methods are invoked on siginficant events and several
    148      * accessor methods are expected to provide the ViewDragHelper with more information
    149      * about the state of the parent view upon request. The callback also makes decisions
    150      * governing the range and draggability of child views.
    151      */
    152     public static abstract class Callback {
    153         /**
    154          * Called when the drag state changes. See the <code>STATE_*</code> constants
    155          * for more information.
    156          *
    157          * @param state The new drag state
    158          *
    159          * @see #STATE_IDLE
    160          * @see #STATE_DRAGGING
    161          * @see #STATE_SETTLING
    162          */
    163         public void onViewDragStateChanged(int state) {}
    164 
    165         /**
    166          * Called when the captured view's position changes as the result of a drag or settle.
    167          *
    168          * @param changedView View whose position changed
    169          * @param left New X coordinate of the left edge of the view
    170          * @param top New Y coordinate of the top edge of the view
    171          * @param dx Change in X position from the last call
    172          * @param dy Change in Y position from the last call
    173          */
    174         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
    175 
    176         /**
    177          * Called when a child view is captured for dragging or settling. The ID of the pointer
    178          * currently dragging the captured view is supplied. If activePointerId is
    179          * identified as {@link #INVALID_POINTER} the capture is programmatic instead of
    180          * pointer-initiated.
    181          *
    182          * @param capturedChild Child view that was captured
    183          * @param activePointerId Pointer id tracking the child capture
    184          */
    185         public void onViewCaptured(View capturedChild, int activePointerId) {}
    186 
    187         /**
    188          * Called when the child view is no longer being actively dragged.
    189          * The fling velocity is also supplied, if relevant. The velocity values may
    190          * be clamped to system minimums or maximums.
    191          *
    192          * <p>Calling code may decide to fling or otherwise release the view to let it
    193          * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
    194          * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
    195          * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
    196          * and the view capture will not fully end until it comes to a complete stop.
    197          * If neither of these methods is invoked before <code>onViewReleased</code> returns,
    198          * the view will stop in place and the ViewDragHelper will return to
    199          * {@link #STATE_IDLE}.</p>
    200          *
    201          * @param releasedChild The captured child view now being released
    202          * @param xvel X velocity of the pointer as it left the screen in pixels per second.
    203          * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
    204          */
    205         public void onViewReleased(View releasedChild, float xvel, float yvel) {}
    206 
    207         /**
    208          * Called when the child view has been released with a fling.
    209          *
    210          * <p>Calling code may decide to fling or otherwise release the view to let it
    211          * settle into place.</p>
    212          *
    213          * @param releasedChild The captured child view now being released
    214          * @param xvel X velocity of the fling.
    215          * @param yvel Y velocity of the fling.
    216          */
    217         public void onViewFling(View releasedChild, float xvel, float yvel) {}
    218 
    219         /**
    220          * Called when one of the subscribed edges in the parent view has been touched
    221          * by the user while no child view is currently captured.
    222          *
    223          * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
    224          * @param pointerId ID of the pointer touching the described edge(s)
    225          * @see #EDGE_LEFT
    226          * @see #EDGE_TOP
    227          * @see #EDGE_RIGHT
    228          * @see #EDGE_BOTTOM
    229          */
    230         public void onEdgeTouched(int edgeFlags, int pointerId) {}
    231 
    232         /**
    233          * Called when the given edge may become locked. This can happen if an edge drag
    234          * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
    235          * was called. This method should return true to lock this edge or false to leave it
    236          * unlocked. The default behavior is to leave edges unlocked.
    237          *
    238          * @param edgeFlags A combination of edge flags describing the edge(s) locked
    239          * @return true to lock the edge, false to leave it unlocked
    240          */
    241         public boolean onEdgeLock(int edgeFlags) {
    242             return false;
    243         }
    244 
    245         /**
    246          * Called when the user has started a deliberate drag away from one
    247          * of the subscribed edges in the parent view while no child view is currently captured.
    248          *
    249          * @param edgeFlags A combination of edge flags describing the edge(s) dragged
    250          * @param pointerId ID of the pointer touching the described edge(s)
    251          * @see #EDGE_LEFT
    252          * @see #EDGE_TOP
    253          * @see #EDGE_RIGHT
    254          * @see #EDGE_BOTTOM
    255          */
    256         public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
    257 
    258         /**
    259          * Called to determine the Z-order of child views.
    260          *
    261          * @param index the ordered position to query for
    262          * @return index of the view that should be ordered at position <code>index</code>
    263          */
    264         public int getOrderedChildIndex(int index) {
    265             return index;
    266         }
    267 
    268         /**
    269          * Return the magnitude of a draggable child view's horizontal range of motion in pixels.
    270          * This method should return 0 for views that cannot move horizontally.
    271          *
    272          * @param child Child view to check
    273          * @return range of horizontal motion in pixels
    274          */
    275         public int getViewHorizontalDragRange(View child) {
    276             return 0;
    277         }
    278 
    279         /**
    280          * Return the magnitude of a draggable child view's vertical range of motion in pixels.
    281          * This method should return 0 for views that cannot move vertically.
    282          *
    283          * @param child Child view to check
    284          * @return range of vertical motion in pixels
    285          */
    286         public int getViewVerticalDragRange(View child) {
    287             return 0;
    288         }
    289 
    290         /**
    291          * Called when the user's input indicates that they want to capture the given child view
    292          * with the pointer indicated by pointerId. The callback should return true if the user
    293          * is permitted to drag the given view with the indicated pointer.
    294          *
    295          * <p>ViewDragHelper may call this method multiple times for the same view even if
    296          * the view is already captured; this indicates that a new pointer is trying to take
    297          * control of the view.</p>
    298          *
    299          * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
    300          * will follow if the capture is successful.</p>
    301          *
    302          * @param child Child the user is attempting to capture
    303          * @param pointerId ID of the pointer attempting the capture
    304          * @return true if capture should be allowed, false otherwise
    305          */
    306         public abstract boolean tryCaptureView(View child, int pointerId);
    307 
    308         /**
    309          * Restrict the motion of the dragged child view along the horizontal axis.
    310          * The default implementation does not allow horizontal motion; the extending
    311          * class must override this method and provide the desired clamping.
    312          *
    313          *
    314          * @param child Child view being dragged
    315          * @param left Attempted motion along the X axis
    316          * @param dx Proposed change in position for left
    317          * @return The new clamped position for left
    318          */
    319         public int clampViewPositionHorizontal(View child, int left, int dx) {
    320             return 0;
    321         }
    322 
    323         /**
    324          * Restrict the motion of the dragged child view along the vertical axis.
    325          * The default implementation does not allow vertical motion; the extending
    326          * class must override this method and provide the desired clamping.
    327          *
    328          *
    329          * @param child Child view being dragged
    330          * @param top Attempted motion along the Y axis
    331          * @param dy Proposed change in position for top
    332          * @return The new clamped position for top
    333          */
    334         public int clampViewPositionVertical(View child, int top, int dy) {
    335             return 0;
    336         }
    337     }
    338 
    339     private final Runnable mSetIdleRunnable = new Runnable() {
    340         public void run() {
    341             setDragState(STATE_IDLE);
    342         }
    343     };
    344 
    345     /**
    346      * Factory method to create a new ViewDragHelper.
    347      *
    348      * @param forParent Parent view to monitor
    349      * @param cb Callback to provide information and receive events
    350      * @return a new ViewDragHelper instance
    351      */
    352     public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
    353         return new ViewDragHelper(forParent.getContext(), forParent, cb);
    354     }
    355 
    356     /**
    357      * Factory method to create a new ViewDragHelper.
    358      *
    359      * @param forParent Parent view to monitor
    360      * @param sensitivity Multiplier for how sensitive the helper should be about detecting
    361      *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
    362      * @param cb Callback to provide information and receive events
    363      * @return a new ViewDragHelper instance
    364      */
    365     public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
    366         final ViewDragHelper helper = create(forParent, cb);
    367         helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
    368         return helper;
    369     }
    370 
    371     /**
    372      * Apps should use ViewDragHelper.create() to get a new instance.
    373      * This will allow VDH to use internal compatibility implementations for different
    374      * platform versions.
    375      *
    376      * @param context Context to initialize config-dependent params from
    377      * @param forParent Parent view to monitor
    378      */
    379     private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
    380         if (forParent == null) {
    381             throw new IllegalArgumentException("Parent view may not be null");
    382         }
    383         if (cb == null) {
    384             throw new IllegalArgumentException("Callback may not be null");
    385         }
    386 
    387         mParentView = forParent;
    388         mCallback = cb;
    389 
    390         final ViewConfiguration vc = ViewConfiguration.get(context);
    391         final float density = context.getResources().getDisplayMetrics().density;
    392         mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
    393 
    394         mTouchSlop = vc.getScaledTouchSlop();
    395         mMaxVelocity = vc.getScaledMaximumFlingVelocity();
    396         mMinVelocity = vc.getScaledMinimumFlingVelocity();
    397         mScroller = new Scroller(context);
    398     }
    399 
    400     /**
    401      * Set the minimum velocity that will be detected as having a magnitude greater than zero
    402      * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
    403      *
    404      * @param minVel Minimum velocity to detect
    405      */
    406     public void setMinVelocity(float minVel) {
    407         mMinVelocity = minVel;
    408     }
    409 
    410     /**
    411      * Return the currently configured minimum velocity. Any flings with a magnitude less
    412      * than this value in pixels per second. Callback methods accepting a velocity will receive
    413      * zero as a velocity value if the real detected velocity was below this threshold.
    414      *
    415      * @return the minimum velocity that will be detected
    416      */
    417     public float getMinVelocity() {
    418         return mMinVelocity;
    419     }
    420 
    421     /**
    422      * Retrieve the current drag state of this helper. This will return one of
    423      * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
    424      * @return The current drag state
    425      */
    426     public int getViewDragState() {
    427         return mDragState;
    428     }
    429 
    430     /**
    431      * Enable edge tracking for the selected edges of the parent view.
    432      * The callback's {@link Callback#onEdgeTouched(int, int)} and
    433      * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
    434      * for edges for which edge tracking has been enabled.
    435      *
    436      * @param edgeFlags Combination of edge flags describing the edges to watch
    437      * @see #EDGE_LEFT
    438      * @see #EDGE_TOP
    439      * @see #EDGE_RIGHT
    440      * @see #EDGE_BOTTOM
    441      */
    442     public void setEdgeTrackingEnabled(int edgeFlags) {
    443         mTrackingEdges = edgeFlags;
    444     }
    445 
    446     /**
    447      * Return the size of an edge. This is the range in pixels along the edges of this view
    448      * that will actively detect edge touches or drags if edge tracking is enabled.
    449      *
    450      * @return The size of an edge in pixels
    451      * @see #setEdgeTrackingEnabled(int)
    452      */
    453     public int getEdgeSize() {
    454         return mEdgeSize;
    455     }
    456 
    457     /**
    458      * Capture a specific child view for dragging within the parent. The callback will be notified
    459      * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
    460      * capture this view.
    461      *
    462      * @param childView Child view to capture
    463      * @param activePointerId ID of the pointer that is dragging the captured child view
    464      */
    465     public void captureChildView(View childView, int activePointerId) {
    466         if (childView.getParent() != mParentView) {
    467             throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
    468                     "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
    469         }
    470 
    471         mCapturedView = childView;
    472         mActivePointerId = activePointerId;
    473         mCallback.onViewCaptured(childView, activePointerId);
    474         setDragState(STATE_DRAGGING);
    475     }
    476 
    477     /**
    478      * @return The currently captured view, or null if no view has been captured.
    479      */
    480     public View getCapturedView() {
    481         return mCapturedView;
    482     }
    483 
    484     /**
    485      * @return The ID of the pointer currently dragging the captured view,
    486      *         or {@link #INVALID_POINTER}.
    487      */
    488     public int getActivePointerId() {
    489         return mActivePointerId;
    490     }
    491 
    492     /**
    493      * @return The minimum distance in pixels that the user must travel to initiate a drag
    494      */
    495     public int getTouchSlop() {
    496         return mTouchSlop;
    497     }
    498 
    499     /**
    500      * The result of a call to this method is equivalent to
    501      * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
    502      */
    503     public void cancel() {
    504         mActivePointerId = INVALID_POINTER;
    505         clearMotionHistory();
    506 
    507         if (mVelocityTracker != null) {
    508             mVelocityTracker.recycle();
    509             mVelocityTracker = null;
    510         }
    511     }
    512 
    513     /**
    514      * {@link #cancel()}, but also abort all motion in progress and snap to the end of any
    515      * animation.
    516      */
    517     public void abort() {
    518         cancel();
    519         if (mDragState == STATE_SETTLING) {
    520             final int oldX = mScroller.getCurrX();
    521             final int oldY = mScroller.getCurrY();
    522             mScroller.abortAnimation();
    523             final int newX = mScroller.getCurrX();
    524             final int newY = mScroller.getCurrY();
    525             mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
    526         }
    527         setDragState(STATE_IDLE);
    528     }
    529 
    530     /**
    531      * Animate the view <code>child</code> to the given (left, top) position.
    532      * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
    533      * on each subsequent frame to continue the motion until it returns false. If this method
    534      * returns false there is no further work to do to complete the movement.
    535      *
    536      * <p>This operation does not count as a capture event, though {@link #getCapturedView()}
    537      * will still report the sliding view while the slide is in progress.</p>
    538      *
    539      * @param child Child view to capture and animate
    540      * @param finalLeft Final left position of child
    541      * @param finalTop Final top position of child
    542      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
    543      */
    544     public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
    545         mCapturedView = child;
    546         mActivePointerId = INVALID_POINTER;
    547 
    548         return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
    549     }
    550 
    551     /**
    552      * Settle the captured view at the given (left, top) position.
    553      * The appropriate velocity from prior motion will be taken into account.
    554      * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
    555      * on each subsequent frame to continue the motion until it returns false. If this method
    556      * returns false there is no further work to do to complete the movement.
    557      *
    558      * @param finalLeft Settled left edge position for the captured view
    559      * @param finalTop Settled top edge position for the captured view
    560      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
    561      */
    562     public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
    563         if (!mReleaseInProgress) {
    564             throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
    565                     "Callback#onViewReleased");
    566         }
    567 
    568         return forceSettleCapturedViewAt(finalLeft, finalTop,
    569                 (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
    570                 (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
    571     }
    572 
    573     /**
    574      * Settle the captured view at the given (left, top) position.
    575      *
    576      * @param finalLeft Target left position for the captured view
    577      * @param finalTop Target top position for the captured view
    578      * @param xvel Horizontal velocity
    579      * @param yvel Vertical velocity
    580      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
    581      */
    582     private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
    583         final int startLeft = mCapturedView.getLeft();
    584         final int startTop = mCapturedView.getTop();
    585         final int dx = finalLeft - startLeft;
    586         final int dy = finalTop - startTop;
    587 
    588         if (dx == 0 && dy == 0) {
    589             // Nothing to do. Send callbacks, be done.
    590             mScroller.abortAnimation();
    591             setDragState(STATE_IDLE);
    592             return false;
    593         }
    594 
    595         final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
    596         mScroller.startScroll(startLeft, startTop, dx, dy, duration);
    597         mFinalScrollY = startTop + dy;
    598 
    599         setDragState(STATE_SETTLING);
    600         return true;
    601     }
    602 
    603     private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
    604         xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
    605         yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
    606         final int absDx = Math.abs(dx);
    607         final int absDy = Math.abs(dy);
    608         final int absXVel = Math.abs(xvel);
    609         final int absYVel = Math.abs(yvel);
    610         final int addedVel = absXVel + absYVel;
    611         final int addedDistance = absDx + absDy;
    612 
    613         final float xweight = xvel != 0 ? (float) absXVel / addedVel :
    614                 (float) absDx / addedDistance;
    615         final float yweight = yvel != 0 ? (float) absYVel / addedVel :
    616                 (float) absDy / addedDistance;
    617 
    618         int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
    619         int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
    620 
    621         return (int) (xduration * xweight + yduration * yweight);
    622     }
    623 
    624     private int computeAxisDuration(int delta, int velocity, int motionRange) {
    625         if (delta == 0) {
    626             return 0;
    627         }
    628 
    629         final int width = mParentView.getWidth();
    630         final int halfWidth = width / 2;
    631         final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
    632         final float distance = halfWidth + halfWidth *
    633                 distanceInfluenceForSnapDuration(distanceRatio);
    634 
    635         int duration;
    636         velocity = Math.abs(velocity);
    637         if (velocity > 0) {
    638             duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    639         } else {
    640             final float range = (float) Math.abs(delta) / motionRange;
    641             duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
    642         }
    643         return Math.min(duration, MAX_SETTLE_DURATION);
    644     }
    645 
    646     /**
    647      * Clamp the magnitude of value for absMin and absMax.
    648      * If the value is below the minimum, it will be clamped to zero.
    649      * If the value is above the maximum, it will be clamped to the maximum.
    650      *
    651      * @param value Value to clamp
    652      * @param absMin Absolute value of the minimum significant value to return
    653      * @param absMax Absolute value of the maximum value to return
    654      * @return The clamped value with the same sign as <code>value</code>
    655      */
    656     private int clampMag(int value, int absMin, int absMax) {
    657         final int absValue = Math.abs(value);
    658         if (absValue < absMin) return 0;
    659         if (absValue > absMax) return value > 0 ? absMax : -absMax;
    660         return value;
    661     }
    662 
    663     /**
    664      * Clamp the magnitude of value for absMin and absMax.
    665      * If the value is below the minimum, it will be clamped to zero.
    666      * If the value is above the maximum, it will be clamped to the maximum.
    667      *
    668      * @param value Value to clamp
    669      * @param absMin Absolute value of the minimum significant value to return
    670      * @param absMax Absolute value of the maximum value to return
    671      * @return The clamped value with the same sign as <code>value</code>
    672      */
    673     private float clampMag(float value, float absMin, float absMax) {
    674         final float absValue = Math.abs(value);
    675         if (absValue < absMin) return 0;
    676         if (absValue > absMax) return value > 0 ? absMax : -absMax;
    677         return value;
    678     }
    679 
    680     private float distanceInfluenceForSnapDuration(float f) {
    681         f -= 0.5f; // center the values about 0.
    682         f *= 0.3f * Math.PI / 2.0f;
    683         return (float) Math.sin(f);
    684     }
    685 
    686     /**
    687      * Settle the captured view based on standard free-moving fling behavior.
    688      * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
    689      * to continue the motion until it returns false.
    690      *
    691      * @param minLeft Minimum X position for the view's left edge
    692      * @param minTop Minimum Y position for the view's top edge
    693      * @param maxLeft Maximum X position for the view's left edge
    694      * @param maxTop Maximum Y position for the view's top edge
    695      */
    696     public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
    697         if (!mReleaseInProgress) {
    698             throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
    699                     "Callback#onViewReleased");
    700         }
    701 
    702         final int yVelocity = (int) VelocityTrackerCompat
    703                 .getYVelocity(mVelocityTracker, mActivePointerId);
    704         mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
    705                 (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
    706                 yVelocity, minLeft, maxLeft, minTop, maxTop);
    707         mFinalScrollY = yVelocity < 0 ? minTop : maxTop;
    708 
    709         setDragState(STATE_SETTLING);
    710     }
    711 
    712     /**
    713      * Settle the captured view based on standard free-moving fling behavior.
    714      * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
    715      * to continue the motion until it returns false.
    716      *
    717      * @param minLeft Minimum X position for the view's left edge
    718      * @param minTop Minimum Y position for the view's top edge
    719      * @param maxLeft Maximum X position for the view's left edge
    720      * @param maxTop Maximum Y position for the view's top edge
    721      * @param yvel the Y velocity to fling with
    722      */
    723     public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop, int yvel) {
    724         if (!mReleaseInProgress) {
    725             throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
    726                     "Callback#onViewReleased");
    727         }
    728         mScroller.abortAnimation();
    729         mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), 0, yvel,
    730                 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
    731         mFinalScrollY = yvel < 0 ? minTop : maxTop;
    732 
    733         setDragState(STATE_SETTLING);
    734     }
    735 
    736     /**
    737      * Predict how far a fling with {@param yvel} will cause the view to travel from stand still.
    738      * @return predicted y offset
    739      */
    740     public int predictFlingYOffset(int yvel) {
    741         mScroller.abortAnimation();
    742         mScroller.fling(0, 0, 0, yvel, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE,
    743                 Integer.MAX_VALUE);
    744         final int finalY = mScroller.getFinalY();
    745         mScroller.abortAnimation();
    746         return finalY;
    747     }
    748 
    749     /**
    750      * Move the captured settling view by the appropriate amount for the current time.
    751      * If <code>continueSettling</code> returns true, the caller should call it again
    752      * on the next frame to continue.
    753      *
    754      * @param deferCallbacks true if state callbacks should be deferred via posted message.
    755      *                       Set this to true if you are calling this method from
    756      *                       {@link android.view.View#computeScroll()} or similar methods
    757      *                       invoked as part of layout or drawing.
    758      * @return true if settle is still in progress
    759      */
    760     public boolean continueSettling(boolean deferCallbacks) {
    761         if (mDragState == STATE_SETTLING) {
    762             boolean keepGoing = mScroller.computeScrollOffset();
    763             int y = mScroller.getCurrY();
    764 
    765             // Since Scroller's getFinalY() can't be properly set (b/17704016), we need to
    766             // perform clamping of mScroller.getCurrY() here.
    767             if (y - mCapturedView.getTop() > 0) {
    768                 y = Math.min(y, mFinalScrollY);
    769             } else {
    770                 y = Math.max(y, mFinalScrollY);
    771             }
    772             final int dy = y - mCapturedView.getTop();
    773 
    774             if (dy != 0) {
    775                 mCapturedView.offsetTopAndBottom(dy);
    776                 mCallback.onViewPositionChanged(mCapturedView, 0, y, 0, dy);
    777             }
    778 
    779             if (keepGoing && y == mFinalScrollY) {
    780                 // Close enough. The interpolator/scroller might think we're still moving
    781                 // but the user sure doesn't.
    782                 mScroller.abortAnimation();
    783                 keepGoing = mScroller.isFinished();
    784             }
    785 
    786             if (!keepGoing) {
    787                 if (deferCallbacks) {
    788                     mParentView.post(mSetIdleRunnable);
    789                 } else {
    790                     setDragState(STATE_IDLE);
    791                 }
    792             }
    793         }
    794 
    795         return mDragState == STATE_SETTLING;
    796     }
    797 
    798     public void processNestedFling(View target, int yvel) {
    799         mCapturedView = target;
    800         dispatchViewFling(0, yvel);
    801     }
    802 
    803     public int getVelocityMagnitude() {
    804         // Use Math.abs() to ensure this always returns an absolute value, even if the
    805         // ScrollerCompat implementation changes.
    806         return (int) Math.abs(mScroller.getCurrVelocity());
    807     }
    808 
    809     private void dispatchViewFling(float xvel, float yvel) {
    810         mReleaseInProgress = true;
    811         mCallback.onViewFling(mCapturedView, xvel, yvel);
    812         mReleaseInProgress = false;
    813 
    814         if (mDragState == STATE_DRAGGING) {
    815             // onViewReleased didn't call a method that would have changed this. Go idle.
    816             setDragState(STATE_IDLE);
    817         }
    818     }
    819 
    820     /**
    821      * Like all callback events this must happen on the UI thread, but release
    822      * involves some extra semantics. During a release (mReleaseInProgress)
    823      * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
    824      * or {@link #flingCapturedView(int, int, int, int)}.
    825      */
    826     private void dispatchViewReleased(float xvel, float yvel) {
    827         mReleaseInProgress = true;
    828         mCallback.onViewReleased(mCapturedView, xvel, yvel);
    829         mReleaseInProgress = false;
    830 
    831         if (mDragState == STATE_DRAGGING) {
    832             // onViewReleased didn't call a method that would have changed this. Go idle.
    833             setDragState(STATE_IDLE);
    834         }
    835     }
    836 
    837     private void clearMotionHistory() {
    838         if (mInitialMotionX == null) {
    839             return;
    840         }
    841         Arrays.fill(mInitialMotionX, 0);
    842         Arrays.fill(mInitialMotionY, 0);
    843         Arrays.fill(mLastMotionX, 0);
    844         Arrays.fill(mLastMotionY, 0);
    845         Arrays.fill(mInitialEdgesTouched, 0);
    846         Arrays.fill(mEdgeDragsInProgress, 0);
    847         Arrays.fill(mEdgeDragsLocked, 0);
    848         mPointersDown = 0;
    849     }
    850 
    851     private void clearMotionHistory(int pointerId) {
    852         if (mInitialMotionX == null) {
    853             return;
    854         }
    855         mInitialMotionX[pointerId] = 0;
    856         mInitialMotionY[pointerId] = 0;
    857         mLastMotionX[pointerId] = 0;
    858         mLastMotionY[pointerId] = 0;
    859         mInitialEdgesTouched[pointerId] = 0;
    860         mEdgeDragsInProgress[pointerId] = 0;
    861         mEdgeDragsLocked[pointerId] = 0;
    862         mPointersDown &= ~(1 << pointerId);
    863     }
    864 
    865     private void ensureMotionHistorySizeForId(int pointerId) {
    866         if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
    867             float[] imx = new float[pointerId + 1];
    868             float[] imy = new float[pointerId + 1];
    869             float[] lmx = new float[pointerId + 1];
    870             float[] lmy = new float[pointerId + 1];
    871             int[] iit = new int[pointerId + 1];
    872             int[] edip = new int[pointerId + 1];
    873             int[] edl = new int[pointerId + 1];
    874 
    875             if (mInitialMotionX != null) {
    876                 System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
    877                 System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
    878                 System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
    879                 System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
    880                 System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
    881                 System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
    882                 System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
    883             }
    884 
    885             mInitialMotionX = imx;
    886             mInitialMotionY = imy;
    887             mLastMotionX = lmx;
    888             mLastMotionY = lmy;
    889             mInitialEdgesTouched = iit;
    890             mEdgeDragsInProgress = edip;
    891             mEdgeDragsLocked = edl;
    892         }
    893     }
    894 
    895     private void saveInitialMotion(float x, float y, int pointerId) {
    896         ensureMotionHistorySizeForId(pointerId);
    897         mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
    898         mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
    899         mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
    900         mPointersDown |= 1 << pointerId;
    901     }
    902 
    903     private void saveLastMotion(MotionEvent ev) {
    904         final int pointerCount = MotionEventCompat.getPointerCount(ev);
    905         for (int i = 0; i < pointerCount; i++) {
    906             final int pointerId = MotionEventCompat.getPointerId(ev, i);
    907             final float x = MotionEventCompat.getX(ev, i);
    908             final float y = MotionEventCompat.getY(ev, i);
    909             mLastMotionX[pointerId] = x;
    910             mLastMotionY[pointerId] = y;
    911         }
    912     }
    913 
    914     /**
    915      * Check if the given pointer ID represents a pointer that is currently down (to the best
    916      * of the ViewDragHelper's knowledge).
    917      *
    918      * <p>The state used to report this information is populated by the methods
    919      * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
    920      * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not
    921      * been called for all relevant MotionEvents to track, the information reported
    922      * by this method may be stale or incorrect.</p>
    923      *
    924      * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent
    925      * @return true if the pointer with the given ID is still down
    926      */
    927     public boolean isPointerDown(int pointerId) {
    928         return (mPointersDown & 1 << pointerId) != 0;
    929     }
    930 
    931     void setDragState(int state) {
    932         if (mDragState != state) {
    933             mDragState = state;
    934             mCallback.onViewDragStateChanged(state);
    935             if (state == STATE_IDLE) {
    936                 mCapturedView = null;
    937             }
    938         }
    939     }
    940 
    941     /**
    942      * Attempt to capture the view with the given pointer ID. The callback will be involved.
    943      * This will put us into the "dragging" state. If we've already captured this view with
    944      * this pointer this method will immediately return true without consulting the callback.
    945      *
    946      * @param toCapture View to capture
    947      * @param pointerId Pointer to capture with
    948      * @return true if capture was successful
    949      */
    950     boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
    951         if (toCapture == mCapturedView && mActivePointerId == pointerId) {
    952             // Already done!
    953             return true;
    954         }
    955         if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
    956             mActivePointerId = pointerId;
    957             captureChildView(toCapture, pointerId);
    958             return true;
    959         }
    960         return false;
    961     }
    962 
    963     /**
    964      * Tests scrollability within child views of v given a delta of dx.
    965      *
    966      * @param v View to test for horizontal scrollability
    967      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
    968      *               or just its children (false).
    969      * @param dx Delta scrolled in pixels along the X axis
    970      * @param dy Delta scrolled in pixels along the Y axis
    971      * @param x X coordinate of the active touch point
    972      * @param y Y coordinate of the active touch point
    973      * @return true if child views of v can be scrolled by delta of dx.
    974      */
    975     protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
    976         if (v instanceof ViewGroup) {
    977             final ViewGroup group = (ViewGroup) v;
    978             final int scrollX = v.getScrollX();
    979             final int scrollY = v.getScrollY();
    980             final int count = group.getChildCount();
    981             // Count backwards - let topmost views consume scroll distance first.
    982             for (int i = count - 1; i >= 0; i--) {
    983                 // TODO: Add versioned support here for transformed views.
    984                 // This will not work for transformed views in Honeycomb+
    985                 final View child = group.getChildAt(i);
    986                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
    987                         y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
    988                         canScroll(child, true, dx, dy, x + scrollX - child.getLeft(),
    989                                 y + scrollY - child.getTop())) {
    990                     return true;
    991                 }
    992             }
    993         }
    994 
    995         return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
    996                 ViewCompat.canScrollVertically(v, -dy));
    997     }
    998 
    999     /**
   1000      * Check if this event as provided to the parent view's onInterceptTouchEvent should
   1001      * cause the parent to intercept the touch event stream.
   1002      *
   1003      * @param ev MotionEvent provided to onInterceptTouchEvent
   1004      * @return true if the parent view should return true from onInterceptTouchEvent
   1005      */
   1006     public boolean shouldInterceptTouchEvent(MotionEvent ev) {
   1007         final int action = MotionEventCompat.getActionMasked(ev);
   1008         final int actionIndex = MotionEventCompat.getActionIndex(ev);
   1009 
   1010         if (action == MotionEvent.ACTION_DOWN) {
   1011             // Reset things for a new event stream, just in case we didn't get
   1012             // the whole previous stream.
   1013             cancel();
   1014         }
   1015 
   1016         if (mVelocityTracker == null) {
   1017             mVelocityTracker = VelocityTracker.obtain();
   1018         }
   1019         mVelocityTracker.addMovement(ev);
   1020 
   1021         switch (action) {
   1022             case MotionEvent.ACTION_DOWN: {
   1023                 final float x = ev.getX();
   1024                 final float y = ev.getY();
   1025                 final int pointerId = MotionEventCompat.getPointerId(ev, 0);
   1026                 saveInitialMotion(x, y, pointerId);
   1027 
   1028                 final View toCapture = findTopChildUnder((int) x, (int) y);
   1029 
   1030                 // Catch a settling view if possible.
   1031                 if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
   1032                     tryCaptureViewForDrag(toCapture, pointerId);
   1033                 }
   1034 
   1035                 final int edgesTouched = mInitialEdgesTouched[pointerId];
   1036                 if ((edgesTouched & mTrackingEdges) != 0) {
   1037                     mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
   1038                 }
   1039                 break;
   1040             }
   1041 
   1042             case MotionEventCompat.ACTION_POINTER_DOWN: {
   1043                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
   1044                 final float x = MotionEventCompat.getX(ev, actionIndex);
   1045                 final float y = MotionEventCompat.getY(ev, actionIndex);
   1046 
   1047                 saveInitialMotion(x, y, pointerId);
   1048 
   1049                 // A ViewDragHelper can only manipulate one view at a time.
   1050                 if (mDragState == STATE_IDLE) {
   1051                     final int edgesTouched = mInitialEdgesTouched[pointerId];
   1052                     if ((edgesTouched & mTrackingEdges) != 0) {
   1053                         mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
   1054                     }
   1055                 } else if (mDragState == STATE_SETTLING) {
   1056                     // Catch a settling view if possible.
   1057                     final View toCapture = findTopChildUnder((int) x, (int) y);
   1058                     if (toCapture == mCapturedView) {
   1059                         tryCaptureViewForDrag(toCapture, pointerId);
   1060                     }
   1061                 }
   1062                 break;
   1063             }
   1064 
   1065             case MotionEvent.ACTION_MOVE: {
   1066                 // First to cross a touch slop over a draggable view wins. Also report edge drags.
   1067                 final int pointerCount = MotionEventCompat.getPointerCount(ev);
   1068                 for (int i = 0; i < pointerCount; i++) {
   1069                     final int pointerId = MotionEventCompat.getPointerId(ev, i);
   1070                     final float x = MotionEventCompat.getX(ev, i);
   1071                     final float y = MotionEventCompat.getY(ev, i);
   1072                     final float dx = x - mInitialMotionX[pointerId];
   1073                     final float dy = y - mInitialMotionY[pointerId];
   1074 
   1075                     reportNewEdgeDrags(dx, dy, pointerId);
   1076                     if (mDragState == STATE_DRAGGING) {
   1077                         // Callback might have started an edge drag
   1078                         break;
   1079                     }
   1080 
   1081                     final View toCapture = findTopChildUnder((int) x, (int) y);
   1082                     if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
   1083                             tryCaptureViewForDrag(toCapture, pointerId)) {
   1084                         break;
   1085                     }
   1086                 }
   1087                 saveLastMotion(ev);
   1088                 break;
   1089             }
   1090 
   1091             case MotionEventCompat.ACTION_POINTER_UP: {
   1092                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
   1093                 clearMotionHistory(pointerId);
   1094                 break;
   1095             }
   1096 
   1097             case MotionEvent.ACTION_UP:
   1098             case MotionEvent.ACTION_CANCEL: {
   1099                 cancel();
   1100                 break;
   1101             }
   1102         }
   1103 
   1104         return mDragState == STATE_DRAGGING;
   1105     }
   1106 
   1107     /**
   1108      * Process a touch event received by the parent view. This method will dispatch callback events
   1109      * as needed before returning. The parent view's onTouchEvent implementation should call this.
   1110      *
   1111      * @param ev The touch event received by the parent view
   1112      */
   1113     public void processTouchEvent(MotionEvent ev) {
   1114         final int action = MotionEventCompat.getActionMasked(ev);
   1115         final int actionIndex = MotionEventCompat.getActionIndex(ev);
   1116 
   1117         if (action == MotionEvent.ACTION_DOWN) {
   1118             // Reset things for a new event stream, just in case we didn't get
   1119             // the whole previous stream.
   1120             cancel();
   1121         }
   1122 
   1123         if (mVelocityTracker == null) {
   1124             mVelocityTracker = VelocityTracker.obtain();
   1125         }
   1126         mVelocityTracker.addMovement(ev);
   1127 
   1128         switch (action) {
   1129             case MotionEvent.ACTION_DOWN: {
   1130                 final float x = ev.getX();
   1131                 final float y = ev.getY();
   1132                 final int pointerId = MotionEventCompat.getPointerId(ev, 0);
   1133                 final View toCapture = findTopChildUnder((int) x, (int) y);
   1134 
   1135                 saveInitialMotion(x, y, pointerId);
   1136 
   1137                 // Since the parent is already directly processing this touch event,
   1138                 // there is no reason to delay for a slop before dragging.
   1139                 // Start immediately if possible.
   1140                 tryCaptureViewForDrag(toCapture, pointerId);
   1141 
   1142                 final int edgesTouched = mInitialEdgesTouched[pointerId];
   1143                 if ((edgesTouched & mTrackingEdges) != 0) {
   1144                     mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
   1145                 }
   1146                 break;
   1147             }
   1148 
   1149             case MotionEventCompat.ACTION_POINTER_DOWN: {
   1150                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
   1151                 final float x = MotionEventCompat.getX(ev, actionIndex);
   1152                 final float y = MotionEventCompat.getY(ev, actionIndex);
   1153 
   1154                 saveInitialMotion(x, y, pointerId);
   1155 
   1156                 // A ViewDragHelper can only manipulate one view at a time.
   1157                 if (mDragState == STATE_IDLE) {
   1158                     // If we're idle we can do anything! Treat it like a normal down event.
   1159 
   1160                     final View toCapture = findTopChildUnder((int) x, (int) y);
   1161                     tryCaptureViewForDrag(toCapture, pointerId);
   1162 
   1163                     final int edgesTouched = mInitialEdgesTouched[pointerId];
   1164                     if ((edgesTouched & mTrackingEdges) != 0) {
   1165                         mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
   1166                     }
   1167                 } else if (isCapturedViewUnder((int) x, (int) y)) {
   1168                     // We're still tracking a captured view. If the same view is under this
   1169                     // point, we'll swap to controlling it with this pointer instead.
   1170                     // (This will still work if we're "catching" a settling view.)
   1171 
   1172                     tryCaptureViewForDrag(mCapturedView, pointerId);
   1173                 }
   1174                 break;
   1175             }
   1176 
   1177             case MotionEvent.ACTION_MOVE: {
   1178                 if (mDragState == STATE_DRAGGING) {
   1179                     final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
   1180                     final float x = MotionEventCompat.getX(ev, index);
   1181                     final float y = MotionEventCompat.getY(ev, index);
   1182                     final int idx = (int) (x - mLastMotionX[mActivePointerId]);
   1183                     final int idy = (int) (y - mLastMotionY[mActivePointerId]);
   1184 
   1185                     dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
   1186 
   1187                     saveLastMotion(ev);
   1188                 } else {
   1189                     // Check to see if any pointer is now over a draggable view.
   1190                     final int pointerCount = MotionEventCompat.getPointerCount(ev);
   1191                     for (int i = 0; i < pointerCount; i++) {
   1192                         final int pointerId = MotionEventCompat.getPointerId(ev, i);
   1193                         final float x = MotionEventCompat.getX(ev, i);
   1194                         final float y = MotionEventCompat.getY(ev, i);
   1195                         final float dx = x - mInitialMotionX[pointerId];
   1196                         final float dy = y - mInitialMotionY[pointerId];
   1197 
   1198                         reportNewEdgeDrags(dx, dy, pointerId);
   1199                         if (mDragState == STATE_DRAGGING) {
   1200                             // Callback might have started an edge drag.
   1201                             break;
   1202                         }
   1203 
   1204                         final View toCapture = findTopChildUnder((int) x, (int) y);
   1205                         if (checkTouchSlop(toCapture, dx, dy) &&
   1206                                 tryCaptureViewForDrag(toCapture, pointerId)) {
   1207                             break;
   1208                         }
   1209                     }
   1210                     saveLastMotion(ev);
   1211                 }
   1212                 break;
   1213             }
   1214 
   1215             case MotionEventCompat.ACTION_POINTER_UP: {
   1216                 final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
   1217                 if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
   1218                     // Try to find another pointer that's still holding on to the captured view.
   1219                     int newActivePointer = INVALID_POINTER;
   1220                     final int pointerCount = MotionEventCompat.getPointerCount(ev);
   1221                     for (int i = 0; i < pointerCount; i++) {
   1222                         final int id = MotionEventCompat.getPointerId(ev, i);
   1223                         if (id == mActivePointerId) {
   1224                             // This one's going away, skip.
   1225                             continue;
   1226                         }
   1227 
   1228                         final float x = MotionEventCompat.getX(ev, i);
   1229                         final float y = MotionEventCompat.getY(ev, i);
   1230                         if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
   1231                                 tryCaptureViewForDrag(mCapturedView, id)) {
   1232                             newActivePointer = mActivePointerId;
   1233                             break;
   1234                         }
   1235                     }
   1236 
   1237                     if (newActivePointer == INVALID_POINTER) {
   1238                         // We didn't find another pointer still touching the view, release it.
   1239                         releaseViewForPointerUp();
   1240                     }
   1241                 }
   1242                 clearMotionHistory(pointerId);
   1243                 break;
   1244             }
   1245 
   1246             case MotionEvent.ACTION_UP: {
   1247                 if (mDragState == STATE_DRAGGING) {
   1248                     releaseViewForPointerUp();
   1249                 }
   1250                 cancel();
   1251                 break;
   1252             }
   1253 
   1254             case MotionEvent.ACTION_CANCEL: {
   1255                 if (mDragState == STATE_DRAGGING) {
   1256                     dispatchViewReleased(0, 0);
   1257                 }
   1258                 cancel();
   1259                 break;
   1260             }
   1261         }
   1262     }
   1263 
   1264     private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
   1265         int dragsStarted = 0;
   1266         if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
   1267             dragsStarted |= EDGE_LEFT;
   1268         }
   1269         if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
   1270             dragsStarted |= EDGE_TOP;
   1271         }
   1272         if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
   1273             dragsStarted |= EDGE_RIGHT;
   1274         }
   1275         if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
   1276             dragsStarted |= EDGE_BOTTOM;
   1277         }
   1278 
   1279         if (dragsStarted != 0) {
   1280             mEdgeDragsInProgress[pointerId] |= dragsStarted;
   1281             mCallback.onEdgeDragStarted(dragsStarted, pointerId);
   1282         }
   1283     }
   1284 
   1285     private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
   1286         final float absDelta = Math.abs(delta);
   1287         final float absODelta = Math.abs(odelta);
   1288 
   1289         if ((mInitialEdgesTouched[pointerId] & edge) != edge  || (mTrackingEdges & edge) == 0 ||
   1290                 (mEdgeDragsLocked[pointerId] & edge) == edge ||
   1291                 (mEdgeDragsInProgress[pointerId] & edge) == edge ||
   1292                 (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
   1293             return false;
   1294         }
   1295         if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
   1296             mEdgeDragsLocked[pointerId] |= edge;
   1297             return false;
   1298         }
   1299         return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
   1300     }
   1301 
   1302     /**
   1303      * Check if we've crossed a reasonable touch slop for the given child view.
   1304      * If the child cannot be dragged along the horizontal or vertical axis, motion
   1305      * along that axis will not count toward the slop check.
   1306      *
   1307      * @param child Child to check
   1308      * @param dx Motion since initial position along X axis
   1309      * @param dy Motion since initial position along Y axis
   1310      * @return true if the touch slop has been crossed
   1311      */
   1312     private boolean checkTouchSlop(View child, float dx, float dy) {
   1313         if (child == null) {
   1314             return false;
   1315         }
   1316         final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
   1317         final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
   1318 
   1319         if (checkHorizontal && checkVertical) {
   1320             return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
   1321         } else if (checkHorizontal) {
   1322             return Math.abs(dx) > mTouchSlop;
   1323         } else if (checkVertical) {
   1324             return Math.abs(dy) > mTouchSlop;
   1325         }
   1326         return false;
   1327     }
   1328 
   1329     /**
   1330      * Check if any pointer tracked in the current gesture has crossed
   1331      * the required slop threshold.
   1332      *
   1333      * <p>This depends on internal state populated by
   1334      * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
   1335      * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
   1336      * the results of this method after all currently available touch data
   1337      * has been provided to one of these two methods.</p>
   1338      *
   1339      * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
   1340      *                   {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
   1341      * @return true if the slop threshold has been crossed, false otherwise
   1342      */
   1343     public boolean checkTouchSlop(int directions) {
   1344         final int count = mInitialMotionX.length;
   1345         for (int i = 0; i < count; i++) {
   1346             if (checkTouchSlop(directions, i)) {
   1347                 return true;
   1348             }
   1349         }
   1350         return false;
   1351     }
   1352 
   1353     /**
   1354      * Check if the specified pointer tracked in the current gesture has crossed
   1355      * the required slop threshold.
   1356      *
   1357      * <p>This depends on internal state populated by
   1358      * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
   1359      * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
   1360      * the results of this method after all currently available touch data
   1361      * has been provided to one of these two methods.</p>
   1362      *
   1363      * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
   1364      *                   {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
   1365      * @param pointerId ID of the pointer to slop check as specified by MotionEvent
   1366      * @return true if the slop threshold has been crossed, false otherwise
   1367      */
   1368     public boolean checkTouchSlop(int directions, int pointerId) {
   1369         if (!isPointerDown(pointerId)) {
   1370             return false;
   1371         }
   1372 
   1373         final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;
   1374         final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;
   1375 
   1376         final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];
   1377         final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];
   1378 
   1379         if (checkHorizontal && checkVertical) {
   1380             return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
   1381         } else if (checkHorizontal) {
   1382             return Math.abs(dx) > mTouchSlop;
   1383         } else if (checkVertical) {
   1384             return Math.abs(dy) > mTouchSlop;
   1385         }
   1386         return false;
   1387     }
   1388 
   1389     /**
   1390      * Check if any of the edges specified were initially touched in the currently active gesture.
   1391      * If there is no currently active gesture this method will return false.
   1392      *
   1393      * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
   1394      *              {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
   1395      *              {@link #EDGE_ALL}
   1396      * @return true if any of the edges specified were initially touched in the current gesture
   1397      */
   1398     public boolean isEdgeTouched(int edges) {
   1399         final int count = mInitialEdgesTouched.length;
   1400         for (int i = 0; i < count; i++) {
   1401             if (isEdgeTouched(edges, i)) {
   1402                 return true;
   1403             }
   1404         }
   1405         return false;
   1406     }
   1407 
   1408     /**
   1409      * Check if any of the edges specified were initially touched by the pointer with
   1410      * the specified ID. If there is no currently active gesture or if there is no pointer with
   1411      * the given ID currently down this method will return false.
   1412      *
   1413      * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
   1414      *              {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
   1415      *              {@link #EDGE_ALL}
   1416      * @return true if any of the edges specified were initially touched in the current gesture
   1417      */
   1418     public boolean isEdgeTouched(int edges, int pointerId) {
   1419         return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0;
   1420     }
   1421 
   1422     private void releaseViewForPointerUp() {
   1423         mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
   1424         final float xvel = clampMag(
   1425                 VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
   1426                 mMinVelocity, mMaxVelocity);
   1427         final float yvel = clampMag(
   1428                 VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
   1429                 mMinVelocity, mMaxVelocity);
   1430         dispatchViewReleased(xvel, yvel);
   1431     }
   1432 
   1433     private void dragTo(int left, int top, int dx, int dy) {
   1434         int clampedX = left;
   1435         int clampedY = top;
   1436         final int oldLeft = mCapturedView.getLeft();
   1437         final int oldTop = mCapturedView.getTop();
   1438         if (dx != 0) {
   1439             clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
   1440             mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
   1441         }
   1442         if (dy != 0) {
   1443             clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
   1444             mCapturedView.offsetTopAndBottom(clampedY - oldTop);
   1445         }
   1446 
   1447         if (dx != 0 || dy != 0) {
   1448             final int clampedDx = clampedX - oldLeft;
   1449             final int clampedDy = clampedY - oldTop;
   1450             mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
   1451                     clampedDx, clampedDy);
   1452         }
   1453     }
   1454 
   1455     /**
   1456      * Determine if the currently captured view is under the given point in the
   1457      * parent view's coordinate system. If there is no captured view this method
   1458      * will return false.
   1459      *
   1460      * @param x X position to test in the parent's coordinate system
   1461      * @param y Y position to test in the parent's coordinate system
   1462      * @return true if the captured view is under the given point, false otherwise
   1463      */
   1464     public boolean isCapturedViewUnder(int x, int y) {
   1465         return isViewUnder(mCapturedView, x, y);
   1466     }
   1467 
   1468     /**
   1469      * Determine if the supplied view is under the given point in the
   1470      * parent view's coordinate system.
   1471      *
   1472      * @param view Child view of the parent to hit test
   1473      * @param x X position to test in the parent's coordinate system
   1474      * @param y Y position to test in the parent's coordinate system
   1475      * @return true if the supplied view is under the given point, false otherwise
   1476      */
   1477     public boolean isViewUnder(View view, int x, int y) {
   1478         if (view == null) {
   1479             return false;
   1480         }
   1481         return x >= view.getLeft() &&
   1482                 x < view.getRight() &&
   1483                 y >= view.getTop() &&
   1484                 y < view.getBottom();
   1485     }
   1486 
   1487     /**
   1488      * Find the topmost child under the given point within the parent view's coordinate system.
   1489      * The child order is determined using {@link Callback#getOrderedChildIndex(int)}.
   1490      *
   1491      * @param x X position to test in the parent's coordinate system
   1492      * @param y Y position to test in the parent's coordinate system
   1493      * @return The topmost child view under (x, y) or null if none found.
   1494      */
   1495     public View findTopChildUnder(int x, int y) {
   1496         final int childCount = mParentView.getChildCount();
   1497         for (int i = childCount - 1; i >= 0; i--) {
   1498             final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
   1499             if (x >= child.getLeft() && x < child.getRight() &&
   1500                     y >= child.getTop() && y < child.getBottom()) {
   1501                 return child;
   1502             }
   1503         }
   1504         return null;
   1505     }
   1506 
   1507     private int getEdgesTouched(int x, int y) {
   1508         int result = 0;
   1509 
   1510         if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
   1511         if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
   1512         if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
   1513         if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
   1514 
   1515         return result;
   1516     }
   1517 
   1518     /**
   1519      * Prepares the {@link ViewDragHelper} for the beginning of a nested scroll.
   1520      *
   1521      * @param target The child view that is dispatching the nested scroll.
   1522      */
   1523     public void startNestedScroll(View target) {
   1524         if (mVelocityTracker == null) {
   1525             mVelocityTracker = VelocityTracker.obtain();
   1526         }
   1527         setDragState(STATE_DRAGGING);
   1528         mCapturedView = target;
   1529     }
   1530 
   1531     /**
   1532      * Informs the {@link ViewDragHelper} that a nested scroll has ended.
   1533      *
   1534      * @param target The child view that is dispatching the nested scroll.
   1535      */
   1536     public void stopNestedScroll(View target) {
   1537         mCapturedView = target;
   1538         dispatchViewReleased(0, 0);
   1539     }
   1540 
   1541     /**
   1542      * Update the {@link ViewDragHelper} with a new nested scrolling event.
   1543      *
   1544      * @param target The child view that is dispatching the nested scroll.
   1545      * @param dx The x distance scrolled on the child, in pixels.
   1546      * @param dy The y distance scroll on the child, in pixels.
   1547      * @param consumed An int array for the {@link ViewDragHelper} to report back the scroll
   1548      *         deltas that it consumed.
   1549      */
   1550     public void processNestedScroll(View target, int dx, int dy, int[] consumed) {
   1551         final int targetX = mCapturedView.getLeft() + dx;
   1552         final int targetY = mCapturedView.getTop() + dy;
   1553         dragTo(targetX, targetY, dx, dy);
   1554         if (consumed != null) {
   1555             final int unconsumedX = targetX - mCapturedView.getLeft();
   1556             final int unconsumedY = targetY - mCapturedView.getTop();
   1557             consumed[0] = unconsumedX - dx;
   1558             consumed[1] = unconsumedY - dy;
   1559         }
   1560     }
   1561 
   1562 }
   1563