Android Scroller realizes elastic sliding

First look at the implementation effect, you can see that when our finger is released, the picture will gradually slide to the initial position instead of jumping directly to the center point.

Code

When the finger touches the view, that is, when the TouchEvent bit MotionEvent.ACTION_DOWN, the starting coordinate position is recorded. At the same time, the view is still performing animation when the finger is pressed on the screen again, so when the animation is still executing, we need to move the animation stop.

                if (!mScroller.isFinished()) {
    
    
                    mScroller.abortAnimation();
                }
                mStartX = (int) event.getX();
                mStartY = (int) event.getY();

Then when the user's finger slides on the screen, that is, when the event is MotionEvent.ACTION_MOVE, we need to move the position of the view. Here I use the scrollBy method to move the view.

                int curX = (int) event.getX();
                int curY = (int) event.getY();
                Log.i(TAG, "onTouchEvent: curX" + curX + "curY" + curY);
                int delX = curX - mStartX;
                int delY = curY - mStartY;
                mStartX = curX;
                mStartY = curY;
                mViewGroup.scrollBy(-delX, -delY);

Why is there a mViewGroup in front of when using scrollBy to move the position, because when we use scrollBy/scrollTo, we actually move the content in the view, so when we want to move the view itself, we need to get the parent of the view. , And then move the content in the parent, that is, the View we need to move. At the same time, we can see that our scrollBy method takes a negative number for the changed value. This is due to the calculation method of the two attributes of the sliding distance calculation inside the View is the same as we usually use Just the opposite.
Insert picture description here
mScrollX is used to record the distance of horizontal scrolling: the calculation method of this property is: the left edge position of the view minus the left edge position of the view content,

so when we slide the view to the right, we need to take the negative value of the change distance.
Insert picture description here
mScrollY is used to calculate the distance of vertical scrolling: The calculation method of this property is: the position of the top edge of the view minus the position of the top edge of the view content.

Then when the finger is lifted, the event is MotionEvent.ACTION_UP, we need to change the view Move smoothly to the starting position.

  case MotionEvent.ACTION_UP:
                mScroller.startScroll(mViewGroup.getScrollX(), mViewGroup.getScrollY(),
                        -mViewGroup.getScrollX(), -mViewGroup.getScrollY(), 1000);
                invalidate();// 在ui线程中调用
                break;

    @Override
    public void computeScroll() {
    
    
        if (mScroller.computeScrollOffset()) {
    
    
            mViewGroup.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();// 在非ui线程中调用
        }
    }

Here we use the scroller to move smoothly. Check the source code of startScroll. This function does not actually do anything. Insert picture description here
The function sets some parameters and does not move the view position. View movement is actually triggered by the invalidate() below, because invalidate() will redraw the view. When redrawing, the draw() method of the view itself will be called, and the draw method will call the computeScroll() method. , In the computeScroll() method, we first judge whether the current movement is over, if it is not over, move to the current animation position through getCurrX(), getCurrY(), and then redraw the view again, then continue to call draw, continue the above In the process, computeScrollOffset() returns false until the scroller ends.

Complete code

When using, you only need to place this view in xml and configure a picture background

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main2Activity">

    <com.example.recyclerviewlearn.CustomizeImageView
        android:layout_centerInParent="true"
        android:background="@drawable/ic_launcher_background"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

Below is the code for custom ImageView

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.Scroller;

public class CustomizeImageView extends androidx.appcompat.widget.AppCompatImageView {
    
    
    private static final String TAG = "CustomizeImageView";

    private ViewGroup mViewGroup;

    private int mStartX = 0;

    private int mStartY = 0;

    private Scroller mScroller = new Scroller(this.getContext());

    public CustomizeImageView(Context context) {
    
    
        super(context);
    }

    public CustomizeImageView(Context context, AttributeSet attrs) {
    
    
        super(context, attrs);
    }

    public CustomizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onAttachedToWindow() {
    
    
        super.onAttachedToWindow();
        mViewGroup = (ViewGroup) getParent();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        Log.i(TAG, "onTouchEvent: ");
        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
    
    
                    mScroller.abortAnimation();
                }
                mStartX = (int) event.getX();
                mStartY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "onTouchEvent: startX" + mStartX + "mStartY" + mStartY);
                int curX = (int) event.getX();
                int curY = (int) event.getY();
                Log.i(TAG, "onTouchEvent: curX" + curX + "curY" + curY);
                int delX = curX - mStartX;
                int delY = curY - mStartY;
                mStartX = curX;
                mStartY = curY;
                Log.i(TAG, "onTouchEvent: ACTION_MOVE");
                mViewGroup.scrollBy(-delX, -delY);
                break;
            case MotionEvent.ACTION_UP:
                mScroller.startScroll(mViewGroup.getScrollX(), mViewGroup.getScrollY(),
                        -mViewGroup.getScrollX(), -mViewGroup.getScrollY(), 1000);
                invalidate();
                break;
            default:
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
    
    
        if (mScroller.computeScrollOffset()) {
    
    
            mViewGroup.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
}

Guess you like

Origin blog.csdn.net/liu_12345_liu/article/details/107297224