One of the most common features added to a custom ViewGroup is the ability to drag subviews. If your event distribution and handling fundamentals are solid, you can do this yourself. Fortunately, the system provides a utility class, ViewDragHelper, which provides a framework for implementing this functionality, thus greatly improving the efficiency of development.

This article not only shows you how to use the utility class, but also analyzes its design principles. Only grasp the principle, in practice to do the same should change.

This article requires you to have a basic understanding of event distribution and handling, and if you don’t, you can refer to my previous three articles

  1. View event handling for event distribution
  2. ViewGroup event distribution and handling source analysis
  3. How to write event handling code

If you’re not familiar with the process of event distribution and handling, you may have learned how to use the ViewDragHelper class from this article, but not the essence of it.

ViewDragHelper implements event handling

Since ViewDragHelper is a tool framework class, the handling of events must be encapsulated as well. Suppose you have a custom ViewGroup class called VDHLayout. Let’s look at how to use the ViewDragHelper class to implement event handling.

public class VDHLayout extends ViewGroup {
    ViewDragHelper mViewDragHelper;
    public VDHLayout(Context context) {
        this(context, null);
    }

    public VDHLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Create a ViewDragHelper object with callback parameters to control the dragging of child views
        mViewDragHelper = ViewDragHelper.create(this.new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                return false; }}); }@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            // Make it simple and only operate the first child View
            View first = getChildAt(0); first.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + first.getMeasuredWidth(), getPaddingTop() + first.getMeasuredHeight()); }}@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // Use ViewDragHelper to determine if truncation is needed
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Use ViewDragHelper to handle drag and drop of child views
        mViewDragHelper.processTouchEvent(event);
        return true; }}Copy the code

VDHLayout inherits from ViewGroup, and for simplicity, only its first child View is laid out, which is what we do in onLayout().

The code for handling events is in the onInterceptTouchEvent() and onTouchEvent() methods. As you can see from the code, With ViewDragHelper. ShouldInterceptTouchEvent () and ViewDragHelper processTouchEvent () to handle events.

Implement the ViewDragHelper callback

Now that we’ve successfully implemented event handling with the ViewDragHelper, where is the dragging of the child View controlled? This is actually controlled by the callback parameters passed in when the ViewDragHelper object is created. As you can see from the code, we only implemented one method in the callback, tryCaptureView(), which is also required.

According to the principles of event distribution and processing, whether the child View of VDHLayout can handle ACTION_DOWN events is related to the logic of event distribution and processing of VDHLayout. ViewDragHelper callbacks are of course also affected by this, so I’ll show you how to implement callbacks in two parts.

Child views do not handle events

First, let’s look at the case where the View does not handle events.

According to the principle of View event distribution and processing, if a View does not set any listener events, and can not be clicked, or long press, then the View does not handle any events.

It’s a bit abstract in theory, for example, adding an ImageView control to a VDHLayout in an XML layout

<?xml version="1.0" encoding="utf-8"? >
<com.bxll.vdhdemo.VDHLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@mipmap/ic_launcher_round" />

</com.bxll.vdhdemo.VDHLayout>
Copy the code

This ImageView doesn’t listen for any events, it’s not clickable or clickable by default, so it’s a child View that doesn’t handle events.

Now analyze the layout, for example, when the finger click ImageView, due to the View, namely ImageView, not handle events, so ACTION_DOWN events will be after VDHLayout. First onInterceptTouchEvent (), Then pass vdhlayout.onTouchEvent ().

Based on experience with event handling, the real processing logic is actually in vdhLayout.onTouchEvent (), which is implemented as follows

    public boolean onTouchEvent(MotionEvent event) {
        // Use ViewDragHelper to handle drag and drop of child views
        mViewDragHelper.processTouchEvent(event);
        return true;
    }
Copy the code

Since VDHLayout controls the child View drag by touching the event, onTouchEvent() must return true.

. As you can see, is to use ViewDragHelper processTouchEvent () to realize VDHLayout. OnTouchEvent (), Now let’s see ViewDragHelper. ProcessTouchEvent () is how to deal with ACTION_DOWN events

    public void processTouchEvent(@NonNull MotionEvent ev) {
        // ...

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                final int pointerId = ev.getPointerId(0);
                // 1. Find which subview the event is applied to
                final View toCapture = findTopChildUnder((int) x, (int) y);
                // Save the coordinate values
                saveInitialMotion(x, y, pointerId);
                // 2. Try to capture the child View used for dragging
                tryCaptureViewForDrag(toCapture, pointerId);
                // Edge touch callback
                final int edgesTouched = mInitialEdgesTouched[pointerId];
                if((edgesTouched & mTrackingEdges) ! =0) {
                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
                }
                break;
            }

            // ...}}Copy the code

First, find the child View you pressed with your finger through the findTopChildUnder() method

    public View findTopChildUnder(int x, int y) {
        final int childCount = mParentView.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            // The getOrderedChildIndex() callback determines which child View to get
            final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
            if (x >= child.getLeft() && x < child.getRight()
                    && y >= child.getTop() && y < child.getBottom()) {
                returnchild; }}return null;
    }
Copy the code

The principle is simple. The child View is found by the x and y coordinate values. However, we can see that the callback method getOrderedChildIndex() determines which child View is found. As you can see here, it’s not always the top subview that the finger is working on.

Once you find the child View that ACTION_DOWN functions on, try to capture the child View with tryCaptureViewForDrag()

    boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
        if (toCapture == mCapturedView && mActivePointerId == pointerId) {
            // Already done!
            return true;
        }
        // Check whether the child View can be captured by a callback
        if(toCapture ! =null && mCallback.tryCaptureView(toCapture, pointerId)) {
            mActivePointerId = pointerId;
            captureChildView(toCapture, pointerId);
            return true;
        }
        return false;
    }
Copy the code

The tryCaptureView() callback method is used to determine whether the child View can be captured. Then the captured child View can be used to drag.

If it can be captured, captureChildView() is called to notify the child View that it has been captured

    public void captureChildView(@NonNull View childView, int activePointerId) {
        // mCapturedView represents the target to be dragged
        mCapturedView = childView;
        mActivePointerId = activePointerId;
        // The callback notifies the View that it was captured
        mCallback.onViewCaptured(childView, activePointerId);
        // Set the drag state
        setDragState(STATE_DRAGGING);
    }
Copy the code

CaptureChildView () is a callback to onViewCaptured() that lets you know that a child View has been captured.

. Now, to conclude ViewDragHelper processTouchEvent () handling of ACTION_DOWN events, callbacks to do what things (only lists the main callback)

  1. throughgetOrderedChildIndex()Callback, judgmentACTION_DOWNWhich subview it’s acting on.
  2. throughtryCaptureView()Callback to determine whether the child View can be captured.
  3. throughonViewCaptured()Callback to notify which child View is captured.

Now that ACTION_DOWN is handled, let’s look at how ACTION_MOVE events are handled.

Because the son View does not handle events, ACTION_MOVE events to VDHLayout. OnTouchEvent () processing, namely to ViewDragHelper. ProcessTouchEvent () processing.

    public void processTouchEvent(@NonNull MotionEvent ev) {
        // ...

        switch (action) {
            // ...

            case MotionEvent.ACTION_MOVE: {
                if (mDragState == STATE_DRAGGING) {
                    // Determine whether the finger is valid
                    if(! isValidPointerForActionMove(mActivePointerId))break;

                    final int index = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(index);
                    final float y = ev.getY(index);
                    // Get the distance difference between x and y
                    final int idx = (int) (x - mLastMotionX[mActivePointerId]);
                    final int idy = (int) (y - mLastMotionY[mActivePointerId]);
                    // Perform a drag on the target View
                    dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);

                    saveLastMotion(ev);
                } else {
                    // ...
                }
                break;
            }

            // ...}}Copy the code

ViewDragHelper. ProcessTouchEvent () in the handling of ACTION_MOVE, first in the x, y axis moving distance is poor, then through dragTo () method of drag just capture the View.

Notice that the first and second arguments of dragTo() refer to the coordinates to which the target View(the captured subview) should theoretically move.

    private void dragTo(int left, int top, int dx, int dy) {
        // clampedX, clampedY indicates the destination coordinate to which the target View will drag
        int clampedX = left;
        int clampedY = top;
        // Get the starting coordinate of the target View
        final int oldLeft = mCapturedView.getLeft();
        final int oldTop = mCapturedView.getTop();
        if(dx ! =0) {
            // If the drag distance is greater than 0, call back to the x position that the target View will drag to
            clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
            // The target View moves horizontally
            ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
        }
        if(dy ! =0) {
            // If the drag distance is greater than 0, call back to the x position that the target View will drag to
            clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
            // The target View moves horizontally
            ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
        }
        
        if(dx ! =0|| dy ! =0) {
            // Calculate the actual distance difference
            final int clampedDx = clampedX - oldLeft;
            final int clampedDy = clampedY - oldTop;
            // The callback notifies the target View to actually move to (clampedX, clampedY), and the distance difference between the x and y axis to actually move to clampedDx, clampedDymCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); }}Copy the code

X, y direction, as long as any one direction finger drag distance is greater than zero, then through clampViewPositionHorizontal ()/clampViewPositionVertical () callback method, calculating target View of the actual need to drag the end point coordinates.

After calculating the endpoint coordinates with the callback, move the target View to the calculated coordinates.

Finally, as long as the drag distance in the x and y directions is greater than 0, the onViewPositionChanged() callback method is used to inform the target View to which coordinates it is actually dragging to and the difference between the actual drag distances.

. We now understand that ViewDragHelper processTouchEvent ACTION_MOVE () processing, is actually processing target View of drag, it USES the following correction

  1. clampViewPositionHorizontal()andclampViewPositionVertical()Callback to calculate the actual coordinates of the target View drag.
  2. onViewPositionChanged()Callback to tell the target to which coordinates the View was actually dragged to and the actual distance difference between the x and y axes.

Implementation subviews do not handle event callbacks

With that in mind, let’s implement the next callback so that a child View that does not handle events can be dragged, and only in the horizontal direction.

        mViewDragHelper = ViewDragHelper.create(this.new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                // For simplicity, all views can be dragged
                return true;
            }

            /** * Control the movement of the target View in the x direction. * /
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                // No vertical movement allowed
                return 0;
            }

            /** * Control the movement of the target View in the y direction. * /
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                // Do not move horizontally beyond the scope of the parent View
                return Math.min(Math.max(0, left), getWidth() - child.getWidth()); }});Copy the code

Because we don’t allow vertical drag, clampViewPositionHorizontal () will return 0, clampViewPositionVertical () returns a value to control within the scope of the VDHLayout sliding. Results the following

There are a few other callbacks in the previous analysis that can be duplicated according to actual project requirements.

The child View handles the event

Now examine the case where a subview can handle an event. The easiest way to make a subview handle events is to make it clickable, for example

<com.bxll.vdhdemo.VDHLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:clickable="true"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@mipmap/ic_launcher_round" />

</com.bxll.vdhdemo.VDHLayout>
Copy the code

When you run the application again using this layout, you will notice that the ImageView that you can drag can no longer be dragged. This is because the event handling logic has changed, and so has the ViewDragHelper implementation logic.

Because the child View can handle events, so for ACTION_DOWN event, it will only be after VDHLayout. OnInterceptTouchEvent () method, and will not pass VDHLayout. OnTouchEvent () method. The previous code, the VDHLayout. OnInterceptTouchEvent () is by ViewDragHelper shouldInterceptTouchEvent () implementation. However ViewDragHelper. ShouldInterceptTouchEvent () method for ACTION_DOWN just some simple processing, will not be truncated.

So we need to analyze how the ACTION_MOVE are ViewDragHelper. ShouldInterceptTouchEvent truncation ().

    public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
        // ...

        switch (action) {
            // ...

            case MotionEvent.ACTION_MOVE: {
                // ...
                
                final int pointerCount = ev.getPointerCount();
                // Only one finger operation is considered
                for (int i = 0; i < pointerCount; i++) {
                    final int pointerId = ev.getPointerId(i);

                    // If pointer is invalid then skip the ACTION_MOVE.
                    if(! isValidPointerForActionMove(pointerId))continue;

                    final float x = ev.getX(i);
                    final float y = ev.getY(i);
                    final float dx = x - mInitialMotionX[pointerId];
                    final float dy = y - mInitialMotionY[pointerId];
                    
                    final View toCapture = findTopChildUnder((int) x, (int) y);
                    // 1. Determine whether the drag criteria are met
                    final booleanpastSlop = toCapture ! =null && checkTouchSlop(toCapture, dx, dy);
                    // An untruncated case: if the drag criteria do not have the actual drag distance, then the event is not truncated
                    if (pastSlop) {
                        // Get the new and old coordinate values
                        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);
                        // Get the range of drag in the x and y direction by callback
                        final int hDragRange = mCallback.getViewHorizontalDragRange(toCapture);
                        final int vDragRange = mCallback.getViewVerticalDragRange(toCapture);
                        // Do not truncate events without the actual drag distance
                        if ((hDragRange == 0 || (hDragRange > 0 && newLeft == oldLeft))
                                && (vDragRange == 0 || (vDragRange > 0 && newTop == oldTop))) {
                            break; }}// Report edge motion
                    reportNewEdgeDrags(dx, dy, pointerId);
                    if (mDragState == STATE_DRAGGING) {
                        // Callback might have started an edge drag
                        break;
                    }
                    
                    // 2. If the critical drag distance is reached, then try to capture the subview
                    if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                        break;
                    }
                }
                saveLastMotion(ev);
                break; }}// If the child View is captured successfully, the state is set to STATE_DRAGGING, representing the truncation event
        return mDragState == STATE_DRAGGING;
    }
Copy the code

ViewDragHelper. ShouldInterceptTouchEvent () takes into account the multiple fingers, in order to simplify the analysis, only consider a single finger.

The first step is to determine whether the drag condition is met. There are two conditions

  1. The event must be applied to a subview
  2. checkTouchSlop()Returns true

As a rule of thumb in event handling, if an ACTION_MOVE event is to be truncated, it must be conditionally truncated.

The checkTouchSlop() method is used to determine whether the critical drag distance has been reached

    private boolean checkTouchSlop(View child, float dx, float dy) {
        if (child == null) {
            return false;
        }
        // Use the callback method to determine whether drag is allowed in the x and y directions
        final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
        final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;

        // If drag is allowed in the x or Y direction, calculate the drag threshold based on the distance
        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;
        }
        // If drag is not allowed in both the x and y directions, the drag threshold will never be reached
        return false;
    }
Copy the code

First by getViewHorizontalDragRange () and getViewVerticalDragRange () to obtain the x, y direction drag, as long as the greater than zero, means can be in the x, y direction drag. Then, depending on which direction you can drag, you calculate whether the drag distance has reached the critical distance.

Now back to shouldInterceptTouchEvent () method of the second step, when after reaching the drag conditions, call tryCaptureViewForDrag () attempts to capture the target View, this method has been analyzed in front, It first calls tryCaptureView() to determine whether the target View can be dragged, if it can, then onViewCaptured() to notify that the target View has been captured, and finally sets the state to STATE_DRAGGING.

When the state is set to STATE_DRAGGING after, so ViewDragHelper shouldInterceptTouchEvent (), the return value is true, . That is to say VDHLayout onInterceptTouchEvent () truncation ACTION_MOVE events.

VDHLayout. OnInterceptTouchEvent () after truncation ACTION_MOVE events, subsequent ACTION_MOVE events to VDHLayout. OnTouchEvent () method, Also is in the hands of the ViewDragHelper. ProcessTouchEvent () processing. This method, which we analyzed before, handles dragging of the target View.

. So now we come to conclude ViewDragHelper shouldInterceptTouchEvent () in the treatment of the ACTION_MOVE truncation, use what are the key to the callback

  1. getViewHorizontalDragRange()andgetViewVerticalDragRange()Method to determine whether the x and y directions can be dragged. A return value greater than 0 means you can drag.

Implement a callback to the View handling event

Through the analysis of the just now, we know that for a child View can handle events, if want to make it can be drag and must autotype getViewHorizontalDragRange () or getViewVerticalDragRange () callback, used to tell ViewDragHelper, Allows to be dragged in the corresponding direction.

So now, let’s solve the problem that the child View(which handles events) can’t be dragged, and we’ll still only have the child View dragged horizontally

       mViewDragHelper = ViewDragHelper.create(this.new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                // For simplicity, all views can be dragged
                return true;
            }

            /** * Control the movement of the target View in the x direction. * /
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                // No vertical movement allowed
                return 0;
            }

            /** * Control the movement of the target View in the y direction. * /
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                // Do not move horizontally beyond the scope of the parent View
                return Math.min(Math.max(0, left), getWidth() - child.getWidth());
            }

            @Override
            public int getViewHorizontalDragRange(@NonNull View child) {
                // Since only the target View is allowed to drag horizontally in the VDHLayout, the drag range is the width of the VDHLayout minus the target View width
                return getWidth() - child.getWidth();
            }

            @Override
            public int getViewVerticalDragRange(@NonNull View child) {
                // Since vertical drag is not allowed, the drag range is 0
                return 0; }}); }Copy the code

Since we only allow horizontal drag, getViewVerticalDragRange() returns a vertical drag range of 0, GetViewHorizontalDragRange () returns the horizontal drag range is getWidth () – child. GetWidth ().

Edge touch

ViewDragHelper has an edge touch feature, which is relatively simple, so I’m not going to analyze it from the source code, but just from an API perspective.

To trigger the edge of the sliding function, the first to call ViewDragHelper. SetEdgeTrackingEnabled (int edgeFlags) method, edge set which allows tracking. The parameters have several available values

    public static final int EDGE_LEFT = 1 << 0;

    public static final int EDGE_RIGHT = 1 << 1;

    public static final int EDGE_TOP = 1 << 2;

    public static final int EDGE_BOTTOM = 1 << 3;

    public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
Copy the code

There are several callbacks for edge touch

        /** * Called when one of the subscribed edges in the parent view has been touched * by the user while no child view is currently captured. */
        public void onEdgeTouched(int edgeFlags, int pointerId) {}
        
        /**
         * Called when the given edge may become locked. This can happen if an edge drag
         * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
         * was called. This method should return true to lock this edge or false to leave it
         * unlocked. The default behavior is to leave edges unlocked.
         */
        public boolean onEdgeLock(int edgeFlags) {
            return false;
        }    
        
        /** * Called when the user has started a deliberate drag away from one * of the subscribed edges in the parent view while no child view is currently captured. */
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {}        
Copy the code

The timing of these callbacks is clearly explained in the notes, which I will translate

  1. onEdgeTouched(): When no child View is captured and edges are allowed to touch, callback when the user touches the edge.
  2. onEdgeLock(): Used to lock which edge to lock. This callback is inonEdgeTouched()After that, you start dragging what you called before.
  3. onEdgeDragStarted(): Callback when no child View is captured and edges are allowed to touch, when the user has started dragging.

System control DrawerLayout is the use of ViewDragHelper edge sliding function to achieve. For the sake of space, I won’t use an example to show how edge touch works.

ViewDragHelper implements View sliding

The ViewDragHelper also has a View-defined function that is implemented using an OverScroller. There are several ways to do this


    /**
     * 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.
     */    
    public boolean settleCapturedViewAt(int finalLeft, int finalTop) {}
    
    /**
     * 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.
     */
    public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop) {}
    
    /**
     * 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.
     */
    public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {}
Copy the code

As you can see from the comment, all three methods need to call continueSettling() on the next frame refresh, which is consistent with OverScroller.

Now, let’s use the settleCapturedViewAt() method to implement a function that will release the dragging View and return it to the original point.

When the dragged View is released, the onViewReleased() method is called back

public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
    if (mViewDragHelper.settleCapturedViewAt(0.0)) { invalidate(); }}Copy the code

Since OverScroller is used for this implementation, it must be called to redraw. During redrawing, the control’s computeScroll() method is called, and continueSettling() is called here

public void computeScroll(a) {
    if (mViewDragHelper.continueSettling(true)) { invalidate(); }}Copy the code

ContinueSettling () is also an encapsulation of the OverScroller logic, returning true to indicate that the locating operation is still in progress and therefore needs to continue calling the redraw operation.

To understand how this works, you must be familiar with the workings of OverScroller.

In this way, the following effect can be achieved

The end of the

A lot of the fancy View dragging is done with ViewDragHelper. This class is a complete collection of tools that we need to master so that we can implement all kinds of awesome View dragging effects in our own custom Viewgroups.