Android 使用 Scroller 实现平滑滚动

记录使用Scroller实现平滑滚动,效果图如下:
Android 使用 Scroller 实现平滑滚动_第1张图片

一、自定义View中实现View的平滑滚动

public class ScrollerView extends View {

    private Scroller mScroller;
    private Paint mPaint;
    /**
     * 屏幕拖动最小像素
     */
    private int mTouchSlop;
    /**
     * View宽度
     */
    private int width;
    /**
     * View高度
     */
    private int height;
    /**
     * MotionEvent.getX()
     */
    private int mEventX;
    /**
     * MotionEvent.getY()
     */
    private int mEventY;
    private Bitmap mBitmap;
    /**
     * View到屏幕左边距离
     */
    private int mStartX;
    /**
     * View到屏幕顶部距离
     */
    private int mStartY;
    /**
     * View默认大小
     */
    private static int DEFAULT_SIZE = 200;

    public ScrollerView(Context context) {
        this(context, null);
    }

    public ScrollerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mScroller = new Scroller(context);

        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledHoverSlop(configuration);
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            width = MeasureSpec.getSize(widthMeasureSpec);
        } else {
            if (heightMode == MeasureSpec.EXACTLY) {
                width = MeasureSpec.getSize(heightMeasureSpec);
            } else {
                width = DEFAULT_SIZE;
            }
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = MeasureSpec.getSize(heightMeasureSpec);
        } else {
            height = width;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null != mBitmap) {
            Rect src = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
            Rect dst = new Rect(0, 0, width, height);
            canvas.drawBitmap(mBitmap, src, dst, mPaint);
        } else {
            Log.e("zzy", "Bitmap is null");
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mEventX = (int) event.getX();
                mEventY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mStartX = (int) event.getRawX() - mEventX;
                mStartY = (int) event.getRawY() - mEventY;
                layout(mStartX,mStartY,mStartX+width,mStartY+height);
                break;
            case MotionEvent.ACTION_UP:
                startScroller();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()){
            int l = mScroller.getCurrX();
            layout(l,mStartY,l+width,mStartY+height);
            invalidate();
        }
    }

    /**
     * 开始Scroller动画
     */
    private void startScroller(){
        mScroller.forceFinished(true);
        mScroller.startScroll(mStartX, mStartY,-mStartX,0);
        int screenWidth = getScreenWidth();
        // Scroller动画默认250ms,超过屏幕一半时设置为500ms
        if (mStartX > screenWidth / 2){
            mScroller.extendDuration(500);
        }
        invalidate();
    }

    private int getScreenWidth(){
        return getResources().getDisplayMetrics().widthPixels;
    }
}

Scroller其实是个辅助类,本身并不能完成动画的执行。而是帮我们计算随着时间的流逝,动画应该执行的位置值,我们需要获得当前时间的位置,然后调用View位置移动方法,将View移动到该位置,完成动画。

所以,在自定义View中。我们需要调用invalidate()触发View的重绘,并覆写重绘会执行的方法computeScroll()

computeScroll()方法中调用ScrollercomputeScrollOffset()计算当前时间动画应该移动的位置,返回值是动画是否在执行。

通过mScroller.getCurrX()mScroller.getCurrY()获得当前时间的位置。手动调用View位置移动的方法将View的位置移动到当前时间的位置,实现View的滚动。

然后再次调用invalidate()触发刷新。直到computeScrollOffset()返回false,动画执行完成,滚动完成。

二、直接使用Scroller实现View的平滑滚动

我们知道,Scroller会帮我们计算当前时间,插值器返回的值。

而如果直接使用Scroller实现平滑滚动的话,也需要借助带时间的监听器。

这里借助ValueAnimator来实现Scroller平滑滚动

    private Scroller mScroller;
    private ImageView mImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImage = findViewById(R.id.image);

        mScroller =new Scroller(this);
    }

    public void btnStart(View view){
        start();
    }

    private void start(){
        mScroller.forceFinished(false);
        mScroller.extendDuration(500);
        mScroller.startScroll(0,0,400,400);
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (mScroller.computeScrollOffset()){
                    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mImage.getLayoutParams();
                    params.leftMargin = mScroller.getCurrX();
                    params.topMargin = mScroller.getCurrY();
                    mImage.setLayoutParams(params);
                }
            }
        });
        valueAnimator.start();
    }

在ValueAnimator的addUpdateListener中刷新Scroller当前值。并移动位置。效果如下:
Android 使用 Scroller 实现平滑滚动_第2张图片

你可能感兴趣的:(UI)