Android ViewDragHelper Source Parsing

Keywords: Android Mobile github less

In the process of customizing ViewGroup, if it involves dragging and sliding of View, the use of ViewDragHelper should be indispensable. It provides a series of auxiliary methods and related status records for user dragging sub-View, such as edge sliding of Navigation Drawer, side sliding menu of QQ5.x, and sliding back of knowledgeable pages, all can be realized by it, so it is necessary to completely grasp it. Grasp its use.

To fully grasp the use and principle of ViewDragHelper, the best way is to read its source code, so this analysis is available, so that when the impression is blurred, you can quickly review the principle, usage, precautions of ViewDragHelper.

Basic Usage

  1. Call the static factory method create() of ViewDragHelper to create an instance of ViewDragHelper in the construction method of the custom ViewGroup
  2. Implementing ViewDragHelper.Callback
    The most important methods are tryCaptureView(), clampViewPositionVertical(), clampViewPositionHorizontal(), getViewHorizontalDragRange(), getViewVerticalDragRange().
    • In tryCaptureView(), a child View instance under the current touching area is passed as a parameter. If you need to drag and drop the child View under the current touching area, it returns true, otherwise it returns false.
    • clampViewPositionVertical() determines where the child view to be dragged should move in the vertical direction. This method passes three parameters: the child view instance to be dragged, the top value of the expected position child view after moving, and the distance to move. The return value is the top value of the sub-View at the final position, usually directly returning the second parameter.
    • clampViewPositionHorizontal() is the same as clampViewPositionVertical(), except that it occurs horizontally and eventually returns the left value of View.
    • getViewVerticalDragRange() returns a number greater than 0 before dragging the touched View vertically.
    • getViewHorizontalDragRange() is the same as getViewVerticalDragRange(), but only occurs horizontally.
  3. Call and return the shouldInterceptTouchEvent() method of ViewDragHelper in the onInterceptTouchEvent() method
  4. Call the processTouchEvent() method of ViewDragHelper() in the onTouchEvent() method. When the ACTION_DOWN event occurs, if there is no consumption event for the child View to be dragged under the current touch point, it should return true at onTouchEvent(), otherwise the subsequent event will not be received and the drag will not occur.
  5. The above steps have already achieved the effect of sub-View dragging. If you want to achieve the effect of fling (automatically sliding down and stopping at a certain rate after loosening hands when sliding), or automatically sliding to a specified position after loosening hands, you need to implement the computeScroll() method of custom ViewGroup. The method is as follows:

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            postInvalidate();
        }
    }
    In the onViewReleased() method of ViewDragHelper.Callback, the settleCapturedViewAt(), flingCapturedView(), or the smoothSlideViewTo() method is called anywhere.
  6. To achieve the effect of edge dragging, you need to call the setEdgeTrackingEnabled() method of ViewDragHelper to register the edges you want to listen on. The onEdgeDragStarted() method in ViewDragHelper.Callback is then implemented, where captureChildView() is manually called to pass the child View to be dragged.

Specific use of Demo can be found in the last few cases.

Source Code Details

The complete source code for ViewDragHelper is available in GitHub or GrepCode Check it online. In the final summary section, I drew a simple flow chart, combed the call of related methods in the whole touch event transmission overweight, and went to the summary section to see if necessary.

Preparatory knowledge

  1. Understand the coordinate system of View. Android View coordinates getLeft, getRight, getTop, getBottom
  2. Understand the mechanism of multi-touch in Motion Event. android Touch, Learn about MotionEvent(1)
  3. Understanding Scroller class principles, Slide Screen Implementation in Android - Hand-held instructions on how to implement touch-screen and Scroller class details
  4. Understanding the distribution mechanism of Touch events, Andriod details the Touch event distribution mechanism of View,ViewGroup from the source code perspective

Creation of ViewDragHelper instance

ViewDragHelper overloads two create() static methods. First, look at the create() method with two parameters:

/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
    return new ViewDragHelper(forParent.getContext(), forParent, cb);
}

The two parameters of create() are well understood. The first is our custom ViewGroup, and the second is the callback object needed to control the drag and drop of the child View. Crea () calls the ViewDragHelper constructor directly, so let's look at this constructor again.

/**
 * Apps should use ViewDragHelper.create() to get a new instance.
 * This will allow VDH to use internal compatibility implementations for different
 * platform versions.
 *
 * @param context Context to initialize config-dependent params from
 * @param forParent Parent view to monitor
 */
private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
    if (forParent == null) {
        throw new IllegalArgumentException("Parent view may not be null");
    }
    if (cb == null) {
        throw new IllegalArgumentException("Callback may not be null");
    }

    mParentView = forParent;
    mCallback = cb;

    final ViewConfiguration vc = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;
    mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);

    mTouchSlop = vc.getScaledTouchSlop();
    mMaxVelocity = vc.getScaledMaximumFlingVelocity();
    mMinVelocity = vc.getScaledMinimumFlingVelocity();
    mScroller = ScrollerCompat.create(context, sInterpolator);
}

This constructor is private and the only constructor, so you can only create ViewDragHelper instances externally through the create() factory method. This requires that the custom ViewGroup and callback objects we pass cannot be empty, otherwise the exception interrupt program will be thrown directly. Some reference values and auxiliary classes for touch sliding are also initialized here.

  • mParentView and mCallback save the corresponding parameters passed in separately
  • ViewConfiguration class defines a series of time, size, distance constants related to View
  • MEdgeSize represents the range of edge touches. For example, when mEdgeSize is 20dp and the user registers to monitor the left edge touch, the x coordinate of the touch point is less than mParentView.getLeft() + mEdgeSize (i.e., the touch point is within 20dp from the left edge of the container), even if it is the left edge touch, see the getEdgesTouched() method of ViewDragHelper for details.
  • MTouchSlop is a very small distance value. Only when the distance between the front and back touch points exceeds the value of mTouchSlop, can we count the two touches as "sliding". We only do sliding processing at this time. Otherwise, any small distance change we have to deal with will appear too frequent. If the processing process is complex and time-consuming, it will cause interface jamming.
  • MMax Velocity and mMin Velocity are the maximum and minimum rates of fling, in pixels per second.
  • mScroller is an auxiliary class for View scrolling. For a detailed analysis of this class, see the following articles

Look at the create() method for three parameters:

/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param sensitivity Multiplier for how sensitive the helper should be about detecting
 *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
    final ViewDragHelper helper = create(forParent, cb);
    helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
    return helper;
}

The second parameter sensitivity is used to adjust the value of mTouchSlop. The larger the sensitivity, the smaller the mTouchSlop, and the more sensitive the sliding detection is. For example, when sensitivity is 1, the distance between front and rear touch points is more than 20 DP before sliding processing. Now when sensitivity is 2, the distance between front and rear touch points is more than 10 dp.

Handling Touch Events

When the mParentView (custom ViewGroup) is touched, the onInterceptTouchEvent (Motion Event ev) of mParentView is called first, and then the shouldInterceptTouchEvent (Motion Event ev) is called. So let's first look at the ACTION_DOWN part of this method:

/**
 * Check if this event as provided to the parent view's onInterceptTouchEvent should
 * cause the parent to intercept the touch event stream.
 *
 * @param ev MotionEvent provided to onInterceptTouchEvent
 * @return true if the parent view should return true from onInterceptTouchEvent
 */
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    final int actionIndex = MotionEventCompat.getActionIndex(ev);

    if (action == MotionEvent.ACTION_DOWN) {
        // Reset things for a new event stream, just in case we didn't get
        // the whole previous stream.
        cancel();
    }

    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final float x = ev.getX();
            final float y = ev.getY();
            final int pointerId = MotionEventCompat.getPointerId(ev, 0);
            saveInitialMotion(x, y, pointerId);

            final View toCapture = findTopChildUnder((int) x, (int) y);

            // Catch a settling view if possible.
            if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
                tryCaptureViewForDrag(toCapture, pointerId);
            }

            final int edgesTouched = mInitialEdgesTouched[pointerId];
            if ((edgesTouched & mTrackingEdges) != 0) {
                mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
            }
            break;
        }

        // Other case s are omitted for the time being
    }

    return mDragState == STATE_DRAGGING;
}

Look at lines 9 to 21, first of all for the concepts of multi-touch (Motion Event's action index, ACTION_POINTER_DOWN, etc.). See android Touch, Learn about MotionEvent(1).

MVelocity Tracker records the information of touching points, which can be used to calculate the sliding rate later. cancel() is called every time an ACTION_DOWN event occurs, and mVelocity Tracker is emptied again in cancel() method. So mVelocity Tracker records all the ACTION_DOWN events until after the ACTION_UP event (before the next ACTION_DOWN event). Touch point information.

Let's look at the 24-42 line case MotionEvent.ACTION_DOWN section. First, save Initial Motion (x, y, pointerId) is called to save the initial information of gesture, i.e. the coordinates of touch points (x, y) and the number of touch fingers (pointerId) when ACTION_DOWN occurs. If the edge of mParentView is touched, which edge is also recorded. Then call findTopChildUnder((int) x, (int) y); to get the top-most sub-View under the current touch point, and see the source code of findTopChildUnder:

/**
 * Find the topmost child under the given point within the parent view's coordinate system.
 * The child order is determined using {@link Callback#getOrderedChildIndex(int)}.
 *
 * @param x X position to test in the parent's coordinate system
 * @param y Y position to test in the parent's coordinate system
 * @return The topmost child view under (x, y) or null if none found.
 */
public View findTopChildUnder(int x, int y) {
    final int childCount = mParentView.getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
        if (x >= child.getLeft() && x < child.getRight() &&
                y >= child.getTop() && y < child.getBottom()) {
            return child;
        }
    }
    return null;
}

The code is simple and the comments are clear. If there are two sub-views overlapping in the same location, if you want the lower sub-View to be selected, you need to implement the getOrdered Child Index (int index) method in Callback to change the order of finding sub-views; for example, the index of topView (upper View) is 4, the index of bottom View (lower View) is 3, and return directly by default according to the normal traversal search method (getOrdered Child Index (). Back to index), topView will be selected. To get the bottomView selected, you have to write as follows:

public int getOrderedChildIndex(int index) {
    int indexTop = mParentView.indexOfChild(topView);
    int indexBottom = mParentView.indexOfChild(bottomView);
    if (index == indexTop) {
        return indexBottom;
    }
    return index;
}

Lines 32 to 35, you also see a mDragState member variable, which has three values:

  1. STATE_IDLE: All View s are stationary and idle
  2. STATE_DRAGGING: A View is being dragged by the user (the user is interacting with the device)
  3. STATE_SETTLING: A View is in the setup state (the user does not interact), which is the process of auto-scrolling.
    mCapturedView defaults to null, so it won't execute the code here at first. mDragState will execute tryCaptureViewForDrag() only when it is in STATE_SETTLING state. The execution will be analyzed later, and skipped here first.

Lines 37 to 40 call Callback.onEdgeTouched to notify the outside that some edges of mParentView have been touched, and mInitialEdgesTouched is assigned in the saveInitialMotion method just called.

The ACTION_DOWN part is processed, skipping the switch statement block, leaving only return mDragState=== STATE_DRAGGING;. MDragState is not assigned in the ACTION_DOWN section, and its default value is STATE_IDLE, so false is returned here.

Which method should be called next after returning false, according to Andriod details the Touch event distribution mechanism of View,ViewGroup from the source code perspective The parsing in mParentView will then look for the View that responds to the Touch event in all the sub-views of mParentView (the dispatchTouchEvent() method of each sub-View will be called, and onTouchEvent() will normally be called in dispatchTouchEvent).

  • If no child View consumes this event (the dispatch TouchEvent () of the child View returns false), the super. dispatch TouchEvent (ev) of mParentView, that is, dispatch TouchEvent (ev) in View, is invoked, then the onTouchEvent() method of mParentView is invoked, and then the process TouchEvent (MotionEvent) of ViewDragHelper is invoked. ev) method. At this point (when ACTION_DOWN event occurs) onTouchEvent() of mParentView will return true, onTouchEvent() can continue to accept the following events such as ACTION_MOVE, ACTION_UP, otherwise the drag can not be completed (except ACTION_DOWN when other events occur, returning true or false will not affect the acceptance of the next event), because the relevant code of the drag is written in the process TouchEve. The ACTION_MOVE part of NT (). Note that onInterceptTouchEvent() of mParentView will not receive subsequent ACTION_MOVE, ACTION_UP events after returning true.

  • If a child View consumes the ACTION_DOWN event, mParentView's onTouchEvent() will not receive the ACTION_DOWN event, that is, ViewDragHelper's processTouchEvent (Motion Event ev) will not receive the ACTION_DOWN event. However, as long as the View has not invoked request Disallow Intercept TouchEvent (true), the ACTION_MOVE part of mParentView's onIntercept TouchEvent () will still execute. If true is returned at this time to intercept ACTION_MOVE event, the ACTION_MOVE part of processTouchEvent() will execute normally, and dragging will be no problem. How to deal with the ACTION_MOVE part of onInterceptTouchEvent() is discussed later.

Next, the two cases are analyzed one by one.

Assuming that no child View consumes this event, the ACTION_DOWN part of the process TouchEvent (Motion Event ev) will eventually be invoked based on the analysis just made:

/**
 * Process a touch event received by the parent view. This method will dispatch callback events
 * as needed before returning. The parent view's onTouchEvent implementation should call this.
 *
 * @param ev The touch event received by the parent view
 */
public void processTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    final int actionIndex = MotionEventCompat.getActionIndex(ev);

    if (action == MotionEvent.ACTION_DOWN) {
        // Reset things for a new event stream, just in case we didn't get
        // the whole previous stream.
        cancel();
    }

    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final float x = ev.getX();
            final float y = ev.getY();
            final int pointerId = MotionEventCompat.getPointerId(ev, 0);
            final View toCapture = findTopChildUnder((int) x, (int) y);

            saveInitialMotion(x, y, pointerId);

            // Since the parent is already directly processing this touch event,
            // there is no reason to delay for a slop before dragging.
            // Start immediately if possible.
            tryCaptureViewForDrag(toCapture, pointerId);

            final int edgesTouched = mInitialEdgesTouched[pointerId];
            if ((edgesTouched & mTrackingEdges) != 0) {
                mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
            }
            break;
        }
        // Other case s are omitted for the time being
    }
}

This code is basically the same as ACTION_DOWN in shouldInterceptTouchEvent(), except that there are no constraints to call the tryCaptureViewForDrag() method directly. Now let's look at this method:

/**
 * Attempt to capture the view with the given pointer ID. The callback will be involved.
 * This will put us into the "dragging" state. If we've already captured this view with
 * this pointer this method will immediately return true without consulting the callback.
 *
 * @param toCapture View to capture
 * @param pointerId Pointer to capture with
 * @return true if capture was successful
 */
boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
    if (toCapture == mCapturedView && mActivePointerId == pointerId) {
        // Already done!
        return true;
    }
    if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
        mActivePointerId = pointerId;
        captureChildView(toCapture, pointerId);
        return true;
    }
    return false;
}

Callback's tryCaptureView(View child, int pointerId) method is invoked here, which passes the current touched View and touch finger number to the past. In tryCaptureView(), it decides whether it is necessary to drag the currently touched View. If you want to drag the currently touched View, it returns true in tryCaptureView(), letting ViewDragHelper capture the currently touched View, and then The captureChildView(toCapture, pointerId) method is called:

/**
 * Capture a specific child view for dragging within the parent. The callback will be notified
 * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
 * capture this view.
 *
 * @param childView Child view to capture
 * @param activePointerId ID of the pointer that is dragging the captured child view
 */
public void captureChildView(View childView, int activePointerId) {
    if (childView.getParent() != mParentView) {
        throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
                "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
    }

    mCapturedView = childView;
    mActivePointerId = activePointerId;
    mCallback.onViewCaptured(childView, activePointerId);
    setDragState(STATE_DRAGGING);
}

The code is simple. In the captureChildView(toCapture, pointerId), the number of the dragged view and touched finger is recorded, and the onViewCaptured (child View, activePointerId) of Callback is called to inform the external child View that it has been captured, and then the setDragState() is called to set the current state to STATE_DRAGGING. See the DragState () source code:

void setDragState(int state) {
    if (mDragState != state) {
        mDragState = state;
        mCallback.onViewDragStateChanged(state);
        if (mDragState == STATE_IDLE) {
            mCapturedView = null;
        }
    }
}

When the state changes, onViewDragStateChanged() of Callback is called to notify the state of the change.

Assuming that the onTouchEvent() of mParentView returns true after ACTION_DOWN occurs, then the ACTION_MOVE section is executed:

public void processTouchEvent(MotionEvent ev) {

    switch (action) {
        // Eliminate other case s...
        
        case MotionEvent.ACTION_MOVE: {
            if (mDragState == STATE_DRAGGING) {
                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                final float x = MotionEventCompat.getX(ev, index);
                final float y = MotionEventCompat.getY(ev, index);
                final int idx = (int) (x - mLastMotionX[mActivePointerId]);
                final int idy = (int) (y - mLastMotionY[mActivePointerId]);

                dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);

                saveLastMotion(ev);
            } else {
                // Check to see if any pointer is now over a draggable view.
                final int pointerCount = MotionEventCompat.getPointerCount(ev);
                for (int i = 0; i < pointerCount; i++) {
                    final int pointerId = MotionEventCompat.getPointerId(ev, i);
                    final float x = MotionEventCompat.getX(ev, i);
                    final float y = MotionEventCompat.getY(ev, i);
                    final float dx = x - mInitialMotionX[pointerId];
                    final float dy = y - mInitialMotionY[pointerId];

                    reportNewEdgeDrags(dx, dy, pointerId);
                    if (mDragState == STATE_DRAGGING) {
                        // Callback might have started an edge drag.
                        break;
                    }

                    final View toCapture = findTopChildUnder((int) x, (int) y);
                    if (checkTouchSlop(toCapture, dx, dy) &&
                            tryCaptureViewForDrag(toCapture, pointerId)) {
                        break;
                    }
                }
                saveLastMotion(ev);
            }
            break;
        }

        // Eliminate other case s...
    }
}

It's important to note that if you don't let go, this part of the code will always be called. The only place to call setDragState(STATE_DRAGGING) is tryCaptureViewForDrag(), which was called in ACTION_DOWN just now. Now there are two different situations.
If you just captured the View to drag in ACTION_DOWN, then execute the if part of the code, which will be parsed later, considering the situation that you did not capture. Without capture, mDragState is still STATE_IDLE, and then executes the else part of the code. The main thing here is to check if any finger touches the View to be dragged, try to capture it when touched, and then make mDragState STATE_DRAGGING, and then execute the if part of the code. There are two other methods involved in Callback, which need to be parsed. They are report New EdgeDrags () and check TouchSlop (). First, look at report New EdgeDrags ():

private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
    int dragsStarted = 0;
    if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
        dragsStarted |= EDGE_LEFT;
    }
    if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
        dragsStarted |= EDGE_TOP;
    }
    if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
        dragsStarted |= EDGE_RIGHT;
    }
    if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
        dragsStarted |= EDGE_BOTTOM;
    }

    if (dragsStarted != 0) {
        mEdgeDragsInProgress[pointerId] |= dragsStarted;
        mCallback.onEdgeDragStarted(dragsStarted, pointerId);
    }
}

All four edges are checked once to see if drags occur on some edges. If drags occur, the edges of drags are recorded in mEdgeDragsInProgress, and then the onEdgeDragStarted (int edge Flags, int pointerId) of Callback is called to notify an edge that drags begin to occur. Although reportNewEdgeDrags() is called many times (because the ACTION_MOVE part of processTouchEvent() executes many times), mCallback.onEdgeDragStarted(dragsStarted, The pointerId will only be called once, depending on the checkNewEdgeDrag() method:

private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
    final float absDelta = Math.abs(delta);
    final float absODelta = Math.abs(odelta);

    if ((mInitialEdgesTouched[pointerId] & edge) != edge  || (mTrackingEdges & edge) == 0 ||
            (mEdgeDragsLocked[pointerId] & edge) == edge ||
            (mEdgeDragsInProgress[pointerId] & edge) == edge ||
            (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
        return false;
    }
    if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
        mEdgeDragsLocked[pointerId] |= edge;
        return false;
    }
    return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}
  • checkNewEdgeDrag() returns true to indicate that the drag begins at the specified edge (edge).
  • The two parameters of the method, Delta and odelta, need to be explained. o in odelta should represent opposite. What does this mean? Take checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT) called in report New EdgeDrags () as an example, we need to monitor the touch of the left edge, so we mainly monitor the change in the direction of x axis, where delta is dx, odelta is dy, that is to say, delta is Modelta refers to the change in the direction we mainly monitor. It is a change in the other direction. To judge whether the change in the false direction is much larger than the change in the main direction, we need the value of the distance change in the other direction.
  • MInitial Edges Touched is generated in saveInitial Motion () of the ACTION_DOWN section, and the edges touched by ACTION_DOWN are recorded in mInitial Edges Touched. If ACTION_DOWN occurs without touching the edge, or if the touched edge is not the specified edge, it returns directly to false.
  • MTracking Edges is set by setEdgeTracking Enabled (int edge Flags). When we want to track edge touches, we need to call setEdgeTracking Enabled (int edge Flags). If we haven't called it, we return to false directly.
  • MEdgeDragsLocked, which has been quoted many times in this method, is only assigned in the entire ViewDragHelper at line 12, so the default value is 0, and line 6 mEdgeDragsLocked [pointerId] & edge) == the result of edge execution is false. Let's jump to 11 to 14 lines and see if absDelta < absODelta * 0.5f means checking whether the distance moving in the secondary direction is far greater than the distance moving in the main direction. If the onEdgeLock(edge) of Callback is called again to check whether an edge needs to be locked. If an edge is locked, the edge will not be recorded in mEdgeDrags Ingress even if touched. No, you won't receive an onEdgeDragStarted() notification from Callback. The locked edge is recorded in the mEdgeDragsLocked variable. When this method is called again, it will be judged in line 6. If a given edge is locked in line 6, it will return to false directly.
  • Back to line 7 (mEdgeDragsInProgress [pointerId] & edge) == edge, mEdgeDragsInProgress saves the edges of drag events that have occurred. If a given edge has been saved, there is no need to detect anything else and go back to false directly.
  • Line 8 (absDelta <= mTouchSlop & absODelta <= mTouchSlop) is very simple. Is it too small to handle this move?
  • When the last sentence returns, check again whether the given edge has been recorded, ensuring that each edge only calls mCallback.onEdgeDragStarted(dragsStarted, pointerId) of reportNewEdgeDrags once.

Let's look again at the checkTouchSlop() method:

/**
 * Check if we've crossed a reasonable touch slop for the given child view.
 * If the child cannot be dragged along the horizontal or vertical axis, motion
 * along that axis will not count toward the slop check.
 *
 * @param child Child to check
 * @param dx Motion since initial position along X axis
 * @param dy Motion since initial position along Y axis
 * @return true if the touch slop has been crossed
 */
private boolean checkTouchSlop(View child, float dx, float dy) {
    if (child == null) {
        return false;
    }
    final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
    final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;

    if (checkHorizontal && checkVertical) {
        return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
    } else if (checkHorizontal) {
        return Math.abs(dx) > mTouchSlop;
    } else if (checkVertical) {
        return Math.abs(dy) > mTouchSlop;
    }
    return false;
}

This method is mainly to check whether the distance of finger movement exceeds the minimum distance triggered to handle mobile events (mTouchSlop). Note that dx and dy refer to the distance between the current touch point and the point touched by ACTION_DOWN. First, check whether the getView Horizontal DragRange (child) and getView Vertical DragRange (child) of Callback are greater than 0. If you want a view to slide in a certain direction, return the number greater than 0 in the corresponding method in that direction. Otherwise, in the ACTION_MOVE part of processTouchEvent(), tryCaptureViewForDrag() will not be called to capture the currently touched View, and dragging will not be possible.

Back to the ACTION_MOVE section of processTouchEvent(), suppose that our fingers have slid onto the View that can be captured, and we have also implemented the relevant methods in Callback, so that tryCaptureViewForDrag() can capture the touched View normally. The next time ACTION_MOVE is executed, if part of the code is started to call dragTo() to mCaptureVi incessantly. The dragTo() method is the dragTo() method.

private void dragTo(int left, int top, int dx, int dy) {
    int clampedX = left;
    int clampedY = top;
    final int oldLeft = mCapturedView.getLeft();
    final int oldTop = mCapturedView.getTop();
    if (dx != 0) {
        clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
        mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
    }
    if (dy != 0) {
        clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
        mCapturedView.offsetTopAndBottom(clampedY - oldTop);
    }

    if (dx != 0 || dy != 0) {
        final int clampedDx = clampedX - oldLeft;
        final int clampedDy = clampedY - oldTop;
        mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
                clampedDx, clampedDy);
    }
}

The parameters DX and Dy are the distance between two ACTION_MOVE movements. The left and top are mCapturedView. getLeft()+dx, mCapturedView. getTop ()+dy, which is the coordinate of the desired movement. For those who do not understand the getLeft() method of View, see Android View coordinates getLeft, getRight, getTop, getBottom.

Here, the mCapturedView is moved by calling offsetLeft AndRight () and offsetTopAndBottom(). These two methods are defined in the View. Looking at their source code, we can see that the inner part of the mCapturedView is to change the coordinate position of the View in the parent container to achieve the effect of moving the View. So if we call the layout (int, int, t) of the mCapturedView, we can change the coordinate position of the mLeft, mRight, mTop and mBottom in the parent container. The method of int r, int b) can also achieve the effect of mobile View.

Where to move is determined by clampViewPosition Horizontal () and clampViewPositionVertical() of Callback. If you don't want to move horizontally, simply return child.getLeft() in clampViewPosition Horizontal (View child, int left, int dx), so that the value of clampedX - old Left is 0. Here, call mCapturedView. offsetLeft Ands (Right pedpedX). - Old Left) It won't work. The same is true in the vertical direction.

Finally, Callback's onViewPositionChanged(mCapturedView, clampedX, clampedY,clampedDx, clampedDy) is called to notify the captured View that its position has been changed, and the final coordinates (clampedX, clampedY) and the final moving distance (clampedDx, clampedDy) are passed over.

Even if the ACTION_MOVE part is over, the next step should be that the user releases his hands to trigger ACTION_UP, or that the subsequent ACTION_MOVE is intercepted by the upper View of mParentView and receives ACTION_CANCEL. Let's look at the two parts together:

public void processTouchEvent(MotionEvent ev) {
    // ellipsis

    switch (action) {
        // Eliminate other case s

        case MotionEvent.ACTION_UP: {
            if (mDragState == STATE_DRAGGING) {
                releaseViewForPointerUp();
            }
            cancel();
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            if (mDragState == STATE_DRAGGING) {
                dispatchViewReleased(0, 0);
            }
            cancel();
            break;
        }
    }
}

Both parts reset all status records and notify View to be released. Then look at the source code for releaseViewForPointerUp() and dispatchViewReleased():

private void releaseViewForPointerUp() {
    mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
    final float xvel = clampMag(
            VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
            mMinVelocity, mMaxVelocity);
    final float yvel = clampMag(
            VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
            mMinVelocity, mMaxVelocity);
    dispatchViewReleased(xvel, yvel);
}

dispatchViewReleased() is also called in releaseViewForPointerUp(), but the rate is passed to it, which is traced by mVelocity Tracker of processTouchEvent(). Look at dispatchViewReleased():

/**
 * Like all callback events this must happen on the UI thread, but release
 * involves some extra semantics. During a release (mReleaseInProgress)
 * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
 * or {@link #flingCapturedView(int, int, int, int)}.
 */
private void dispatchViewReleased(float xvel, float yvel) {
    mReleaseInProgress = true;
    mCallback.onViewReleased(mCapturedView, xvel, yvel);
    mReleaseInProgress = false;

    if (mDragState == STATE_DRAGGING) {
        // onViewReleased didn't call a method that would have changed this. Go idle.
        setDragState(STATE_IDLE);
    }
}

Callback's onView Released (mCapturedView, xvel, yvel) is called here to notify that the captured View is released, and there is a mReleaseInProgress around onViewReleased(), which is noteworthy. The commentary says that the only places where you can call ViewDragHelper's settleCapturedViewAt() and flingCapturedView() are in Callback's onViewReleased().

First of all, what are these two methods for? In real life, bowling is played by throwing the ball to the maximum speed, and then suddenly let go. Because of inertia, bowling is thrown out at the initial speed before the final release, until it stops naturally or hits the boundary. This effect is called fling.
Flying CapturedView (int minLeft, int minTop, int maxLeft, int maxTop) is the effect of fling on captured View. Users also have a sliding rate before sliding on the screen to release their hands. Flying also raises the question of not knowing where the View will eventually scroll to. The final position is calculated according to the speed of the last slide when fling is started (the four parameters of fling CapturedView, int minLeft, int minTop, int maxLeft, int maxTop, can limit the scope of the final position). If you want to scroll the View to the specified position, the answer is Use settleCapturedViewAt(int) finalLeft, int finalTop).

Why is the only place where settleCapturedViewAt() and flingCapturedView() can be called is the onViewReleased() of Callback? Look at their source code

/**
 * Settle the captured view at the given (left, top) position.
 * The appropriate velocity from prior motion will be taken into account.
 * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
 * on each subsequent frame to continue the motion until it returns false. If this method
 * returns false there is no further work to do to complete the movement.
 *
 * @param finalLeft Settled left edge position for the captured view
 * @param finalTop Settled top edge position for the captured view
 * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 */
public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
    if (!mReleaseInProgress) {
        throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
                "Callback#onViewReleased");
    }

    return forceSettleCapturedViewAt(finalLeft, finalTop,
            (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
            (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
}

/**
 * Settle the captured view based on standard free-moving fling behavior.
 * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
 * to continue the motion until it returns false.
 *
 * @param minLeft Minimum X position for the view's left edge
 * @param minTop Minimum Y position for the view's top edge
 * @param maxLeft Maximum X position for the view's left edge
 * @param maxTop Maximum Y position for the view's top edge
 */
public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
    if (!mReleaseInProgress) {
        throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
                "Callback#onViewReleased");
    }

    mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
            (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
            (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
            minLeft, maxLeft, minTop, maxTop);

    setDragState(STATE_SETTLING);
}

In both methods, mReleaseInProgress is judged to be false at first, and if it is false, an IllegalStateException exception is thrown. The only time mReleaseInProgress is true is when onViewReleased() is called in dispatchViewReleased().

See Scroller for usage Slide Screen Implementation in Android - Hand-held instructions on how to implement touch-screen and Scroller class details Or self-interpret Scroller source code, the amount of code is not much.

ViewDragHelper also has a way to move View by smoothSlideViewTo (View child, int final Left, int final Top). Look at its source code:

/**
 * Animate the view <code>child</code> to the given (left, top) position.
 * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
 * on each subsequent frame to continue the motion until it returns false. If this method
 * returns false there is no further work to do to complete the movement.
 *
 * <p>This operation does not count as a capture event, though {@link #getCapturedView()}
 * will still report the sliding view while the slide is in progress.</p>
 *
 * @param child Child view to capture and animate
 * @param finalLeft Final left position of child
 * @param finalTop Final top position of child
 * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 */
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
    mCapturedView = child;
    mActivePointerId = INVALID_POINTER;

    boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
    if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
        // If we're in an IDLE state to begin with and aren't moving anywhere, we
        // end up having a non-null capturedView with an IDLE dragState
        mCapturedView = null;
    }

    return continueSliding;
}

You can see that it is not restricted by mReleaseInProgress, so it can be invoked anywhere. The effect is similar to settleCapturedViewAt(), because they eventually call forceSettleCapturedViewAt() to start auto-scrolling, except that settleCapturedViewAt() scrolls View to the final position at the initial speed of the last release, while smoothSlideViewTo() scrolls. The initial velocity is zero. There is a place in forceSettleCapturedViewAt() to call the method in Callback, so look at this method again:

/**
 * Settle the captured view at the given (left, top) position.
 *
 * @param finalLeft Target left position for the captured view
 * @param finalTop Target top position for the captured view
 * @param xvel Horizontal velocity
 * @param yvel Vertical velocity
 * @return true if animation should continue through {@link #continueSettling(boolean)} calls
 */
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
    final int startLeft = mCapturedView.getLeft();
    final int startTop = mCapturedView.getTop();
    final int dx = finalLeft - startLeft;
    final int dy = finalTop - startTop;

    if (dx == 0 && dy == 0) {
        // Nothing to do. Send callbacks, be done.
        mScroller.abortAnimation();
        setDragState(STATE_IDLE);
        return false;
    }

    final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
    mScroller.startScroll(startLeft, startTop, dx, dy, duration);

    setDragState(STATE_SETTLING);
    return true;
}

You can see that the automatic sliding is done by the Scroll class, where the parameters required to call mScroller.startScroll() are generated. Let's look at computeSettleDuration():

private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
    xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
    yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
    final int absDx = Math.abs(dx);
    final int absDy = Math.abs(dy);
    final int absXVel = Math.abs(xvel);
    final int absYVel = Math.abs(yvel);
    final int addedVel = absXVel + absYVel;
    final int addedDistance = absDx + absDy;

    final float xweight = xvel != 0 ? (float) absXVel / addedVel :
            (float) absDx / addedDistance;
    final float yweight = yvel != 0 ? (float) absYVel / addedVel :
            (float) absDy / addedDistance;

    int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
    int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));

    return (int) (xduration * xweight + yduration * yweight);
}

The clampMag() method ensures that the given rate in the parameter is within the normal range. The final rolling time is also computeAxisDuration(), and its parameters show that the final rolling time is affected by dx, xvel and mCallback.getViewHorizontalDragRange(). Look at computeAxisDuration():

private int computeAxisDuration(int delta, int velocity, int motionRange) {
    if (delta == 0) {
        return 0;
    }

    final int width = mParentView.getWidth();
    final int halfWidth = width / 2;
    final float distanceRatio = Math.min(f, (float) Math.abs(delta) / width);
    final float distance = halfWidth + halfWidth *
            distanceInfluenceForSnapDuration(distanceRatio);

    int duration;
    velocity = Math.abs(velocity);
    if (velocity > 0) {
        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    } else {
        final float range = (float) Math.abs(delta) / motionRange;
        duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
    }
    return Math.min(duration, MAX_SETTLE_DURATION);
}

Lines 6-10 don't see clearly. Look directly at lines 14-19. If the given speed velocity is not zero, the time is calculated by dividing the distance by the speed. If the velocity is zero, the time is calculated by dividing the distance to slide by the total motion Range (i.e. the getViewHorizontalDragRange() and the getViewVerticalDragRange() return value) in the Callback. Finally, the calculated time will be filtered. The final time will not exceed MAX_SETTLE_DURATION anyway. The value in the source code is 600 milliseconds, so there is no need to worry about the number of errors returned by getViewHorizontalDragRange() and getViewVerticalDragRange() in the Callback, which will result in too long automatic scrolling time.

When you call settleCapturedViewAt(), flingCapturedView(), and smoothSlideViewTo(), you also need to implement computeScroll():

@Override
public void computeScroll() {
    if (mDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

This falls within the scope of Scroll class usage, for those who don't understand it, see Slide Screen Implementation in Android - Hand-held instructions on how to implement touch-screen and Scroller class details Knowledge Point 2: Introduction to Computer Scroll () Method.

So far, the entire touching process and the important methods of ViewDragHelper have been passed through. Before discussing what should be executed after the ACTION_DOWN part of shouldInterceptTouchEvent() has been executed, there is another situation that has not been explained in detail, that is, the case where a child View consumed the ACTION_DOWN event. Now let's look at this situation.

Assuming that the ACTION_DOWN part of shouldInterceptTouchEvent() is now executed, and that some sub-Views consume the ACTION_DOWN event, then the ACTION_MOVE part of onInterceptTouchEvent() of mParentView will be called next. See Andriod Detailed View,ViewGroup Touch Event Distribution Mechanism from the Source Point of View Then call the ACTION_MOVE part of shouldInterceptTouchEvent() of ViewDragHelper:

public boolean shouldInterceptTouchEvent(MotionEvent ev) {
    // Elimination of ____________.
    
    switch (action) {
        // Eliminate other case s...

        case MotionEvent.ACTION_MOVE: {
            // First to cross a touch slop over a draggable view wins. Also report edge drags.
            final int pointerCount = MotionEventCompat.getPointerCount(ev);
            for (int i = 0; i < pointerCount; i++) {
                final int pointerId = MotionEventCompat.getPointerId(ev, i);
                final float x = MotionEventCompat.getX(ev, i);
                final float y = MotionEventCompat.getY(ev, i);
                final float dx = x - mInitialMotionX[pointerId];
                final float dy = y - mInitialMotionY[pointerId];

                final View toCapture = findTopChildUnder((int) x, (int) y);
                final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
                if (pastSlop) {
                    // check the callback's
                    // getView[Horizontal|Vertical]DragRange methods to know
                    // if you can move at all along an axis, then see if it
                    // would clamp to the same value. If you can't move at
                    // all in every dimension with a nonzero range, bail.
                    final int oldLeft = toCapture.getLeft();
                    final int targetLeft = oldLeft + (int) dx;
                    final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
                            targetLeft, (int) dx);
                    final int oldTop = toCapture.getTop();
                    final int targetTop = oldTop + (int) dy;
                    final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
                            (int) dy);
                    final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
                            toCapture);
                    final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
                    if ((horizontalDragRange == 0 || horizontalDragRange > 0
                            && newLeft == oldLeft) && (verticalDragRange == 0
                            || verticalDragRange > 0 && newTop == oldTop)) {
                        break;
                    }
                }
                reportNewEdgeDrags(dx, dy, pointerId);
                if (mDragState == STATE_DRAGGING) {
                    // Callback might have started an edge drag
                    break;
                }

                if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                    break;
                }
            }
            saveLastMotion(ev);
            break;
        }

        // Eliminate other case s...
    }

    return mDragState == STATE_DRAGGING;
}

If more than one finger touches the screen, check each touch point to see if a view needs to be captured where it is currently touched. findTopChildUnder(int x, int y) is used to find the sub-View at the touch point, and checkTouchSlop(View child, float dx, float dy) is used to check whether the distance between the current touch point and ACTION_DOWN touch point reaches mTouchSlop before capturing the View.
Next, look at the if (pastSlop) {...} section of lines 19 to 41 to check whether dragging can be done in a certain direction. The checking process involves four methods: getView[Horizontal|Vertical]DragRange and clampViewPosition[Horizontal|Vertical]. If getView[Horizontal|Vertical]DragRange returns all 0, it is assumed that no drag will occur. clampViewPosition[Horizontal|Vertical] returns the final position of the captured View, which, if the same as the original position, means that we do not expect it to move, we will assume that there will be no drag. If no drag occurs, it will break directly in 39 lines, and it will not execute subsequent code, which calls tryCaptureViewForDrag(), so no drag will occur, no view will be captured, and no drag will occur.
If you check that you can drag in a certain direction, you call the following tryCaptureViewForDrag() to capture the sub-View. If the capture succeeds, mDragState will become STATE_DRAGGING, shouldInterceptTouchEvent() will return true, onInterceptTouchEvent() of mParentView will return true, and subsequent mobile events will be executed in onTouchEvent() of mParentView and finally executed. This is the ACTION_MOVE part of mParentView's processTouchEvent(), which drags normally.

Look back at the pits left in the ACTION_DOWN section of shouldInterceptTouchEvent():

public boolean shouldInterceptTouchEvent(MotionEvent ev) {
    // Eliminate other parts.
    
    switch (action) {
        // Eliminate other case s...

        case MotionEvent.ACTION_DOWN: {
            // Eliminate other parts.
            
            // Catch a settling view if possible.
            if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
                tryCaptureViewForDrag(toCapture, pointerId);
            }
            
            // Eliminate other parts.
        }

        // Eliminate other case s...
    }

    return mDragState == STATE_DRAGGING;
}

Now it's time to understand when this part of the code will be executed. When the View captured after we release is in the process of auto-scrolling, the user touches the screen again, and then executes tryCaptureViewForDrag() here to try to capture the View. If the capture succeeds, mDragState becomes STATE_DRAGGING, shouldInterceptTouchEvent() returns true, then onInterceptTouchEvent() of mParentView returns true, and then mParentView is executed. The onTouchEvent() of processTouchEvent() executes the ACTION_DOWN part of processTouchEvent(). At this time (when ACTION_DOWN event occurs), onTouchEvent() of mParentView will return true, onTouchEvent() can continue to accept the following ACTION_MOVE, ACTION_UP and other events, otherwise the drag cannot be completed.

So far, the whole event delivery process and the important method of ViewDragHelper have been basically resolved. The sections of ACTION_POINTER_DOWN and ACTION_POINTER_UP of shouldInterceptTouchEvent() and processTouchEvent() are left for the reader to parse themselves.

summary

For the whole process of touching event transmission, I drew a brief flow chart for quick review in the future.

I haven't looked at the multi-touch scenario, but I'm ignoring it here.~

Three ways to turn on auto-scrolling:

  • settleCapturedViewAt(int finalLeft, int finalTop)
    Take the sliding speed before loosening hands as the initial speed, and let the captured View automatically scroll to the specified position. It can only be called in onViewReleased() of Callback.
  • flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
    Take the sliding speed before loosening as the initial velocity, let the captured View fling within the specified range. It can only be called in onViewReleased() of Callback.
  • smoothSlideViewTo(View child, int finalLeft, int finalTop)
    Specifies that a View automatically scrolls to the specified position with an initial speed of 0 and can be invoked anywhere.

Summary of Callback's methods:

  • void onViewDragStateChanged(int state)
    This method is called when the drag state changes. The state state has three values: STATE_IDLE, STATE_DRAGGING and STATE_SETTLING.
    It is called in setDragState(), whereas setDragState() is called in
    • When tryCaptureViewForDrag() successfully captures a child View
      • ACTION_DOWN part of shouldInterceptTouchEvent() captured
      • ACTION_MOVE part of shouldInterceptTouchEvent() captured
      • ACTION_MOVE part of processTouchEvent() captured
    • When you call settleCapturedViewAt(), smoothSlideViewTo(), flingCapturedView()
    • ACTION_UP, ACTION_CANCEL of process TouchEvent ()
    • When the auto-scroll stops (the end of the scroll is detected in continueSettling())
    • When abort() is called externally
  • void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
    This method is called when the position of the dragged View or the automatically scrolling View changes.
    • Called in dragTo() (while being dragged)
    • Called in continueSettling() (when autoscrolling)
    • When abort() is called externally
  • void onViewCaptured(View capturedChild, int activePointerId)
    This method is called when tryCaptureViewForDrag() successfully captures a child View.
    • Successful capture in ACTION_DOWN of shouldInterceptTouchEvent()
    • Captured successfully in ACTION_MOVE of shouldInterceptTouchEvent()
    • Successful capture in ACTION_MOVE of processTouchEvent()
    • Call captureChildView() manually
  • void onViewReleased(View releasedChild, float xvel, float yvel)
    This method is called when you drag the View to release (ACTION_UP of processTouchEvent() or when the event is intercepted by the parent View (ACTION_CANCEL of processTouchEvent().
  • void onEdgeTouched(int edgeFlags, int pointerId)
    This method is called when an ACTION_DOWN or ACTION_POINTER_DOWN event occurs if the edge of the listener is touched. Edge Flags is a combination of EDGE_LEFT, EDGE_TOP, EDGE_RIGHT and EDGE_BOTTOM.
  • boolean onEdgeLock(int edgeFlags)
    Returning true means that the edges corresponding to the locked edge Flags are not notified by onEdgeDragStarted(). By default, returning false does not lock the given edges. The values of edge Flags are EDGE_LEFT, EDGE_TOP, EDGE_RIGHT and EDGE_BOTTOM.
  • void onEdgeDragStarted(int edgeFlags, int pointerId)
    When the ACTION_MOVE event occurs, a gesture that starts dragging on some edges is detected, and the edge is not locked. This method is called. Edge Flags is a combination of EDGE_LEFT, EDGE_TOP, EDGE_RIGHT and EDGE_BOTTOM. You can manually call captureChildView() here to trigger the effect of dragging a child View from the edge.
  • int getOrderedChildIndex(int index)
    This method is invoked when searching for a child View under the current touch point, which is provided with tryCaptureViewForDrag() to try to capture. This method can be overwritten if you need to change the traversal query order of the sub-View, such as making the lower view prior to the upper view selected.
  • int getViewHorizontalDragRange(View child),int getViewVerticalDragRange(View child)
    Returns the farthest distance a given child can be dragged in the corresponding direction, returning 0 by default. When ACTION_DOWN occurs, if the child at the touch point consumes the event and wants to be dragged in a certain direction, it must return the number greater than 0 in the corresponding method.
    There are three places to be called:
    • Called in checkTouchSlop(), the return value is greater than 0 before checking mTouchSlop. checkTouchSlop() is called before tryCaptureViewForDrag() is called in ACTION_MOVE. If checkTouchSlop() fails, you won't capture the View.
    • If ACTION_DOWN occurs, there is a sub-View consumption event at the touch point, which is called in ACTION_MOVE of shouldInterceptTouchEvent(). If the range in both directions is 0 (both methods return 0), the view will not be captured.
    • When smoothSlideViewTo() is called, it is used to calculate how long it takes to scroll automatically. When this time is calculated, if it exceeds the maximum value, the final time will be the maximum value. So don't worry about the problem of computing time caused by the inappropriate number returned in getView[Horizontal|Vertical]DragRange. Just return the number greater than 0.
  • boolean tryCaptureView(View child, int pointerId)
    Called in tryCaptureViewForDrag(), returns true to capture a given child. Where tryCaptureViewForDrag() is called is
    • In ACTION_DOWN of shouldInterceptTouchEvent()
    • ACTION_MOVE in shouldInterceptTouchEvent()
    • ACTION_MOVE in processTouchEvent()
  • int clampViewPositionHorizontal(View child, int left, int dx),int clampViewPositionVertical(View child, int top, int dy)
    When a child is dragged in a certain direction, the corresponding method is called. The return value is the coordinate position after the child moves. clampViewPosition Horizontal () returns the left value after the child moves, and clampViewPositionVertical() returns the top value after the child moves.
    There are two places where the two methods are called:
    • It is called in dragTo(), and dragTo() is called in ACTION_MOVE of processTouchEvent(). Used to get the location of the dragged View to move to.
    • If ACTION_DOWN occurs, there is a sub-View consumption event at the touch point, which is called in ACTION_MOVE of shouldInterceptTouchEvent(). If the original left and top values are returned in both directions, the view will not be captured.

Case Reference

Here are some examples of the application of ViewDragHelper, and we will analyze their source code to practice consolidation.

  1. YoutubeLayout This is the simplest Demo
  2. QQ5.x Side Slide Menu,ResideLayout
  3. SwipeBackLayout,SwipeBack
  4. SlidingUpPanel
  5. DrawerLayout

Other Analytical Articles on ViewDragHelper

Reproduced at http://www.cnblogs.com/lqstayreal/p/4500219.html

Posted by catlover on Tue, 25 Jun 2019 12:41:44 -0700