美文网首页Android 自定义view
高级UI<第四十四篇>:Android Scroller详解

高级UI<第四十四篇>:Android Scroller详解

作者: NoBugException | 来源:发表于2020-02-29 12:50 被阅读0次

    滚动视图的方法有两种:scrollToscrollBy,而Scroller就是它们的辅助工具类,所以Scroller是学好高级UI必不可少的一课。

    (1)scrollTo、scrollBy、getScrollX、getScrollY

    view的内容本身具备滚动的方法,其中滚动方法如下:

    • scrollTo:相对于初始位置移动
    • scrollBy:相对于上次移动的最后位置移动

    这两个方法特别需要注意以下几点:

    • 两者移动的都是view的内容,view本身是不移动的,所以getX和getY的值不会受到这两个方法的影响;
    • 不要再在onDraw中调用这两个方法,避免onDraw方法被重复执行,因为一旦调用这两个方法view会被重绘,onDraw方法会再次执行。

    view内容滚动的方法有了,那么该如何获取view内容被滚动的距离呢?看以下两个方法:

    • getScrollX:获取view的内容在X轴滚动的距离
    • getScrollY:获取view的内容在Y轴滚动的距离

    以上只说到view内容的滚动,那么view本身的移动用什么方法呢?

    答:setXsetY方法。

    本文的重点内容是Scroller,这个辅助类的作用不是view本身的移动,而是view内容的滚动,下面开始简单说明一下Scroller辅助类。

    (2)熟悉Scroller的构造方法
    //默认插值器是ViscousFluidInterpolator
    Scroller mScroller = new Scroller(mContext);
    
    //指定一个插值器
    Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator());
    
    //指定一个插值器,第三个参数表示是否开启“飞轮”效果,也就是多次滚动时速度叠加
    Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator(), false);
    
    (3)熟悉插值器
    图片.png

    Scroller其实就是在scrollTo(x, y)scrollBy(x, y)的基础上添加滚动效果,滚动效果是一个动画,当我们new一个Scroller对象时,就已经指定了一个插值器,下面来说明一下各种插值器:

    • ViscousFluidInterpolator:这是一个默认插值器,当构造Scroller时,如果不传递插值器或者插值器为null时,系统默认使用ViscousFluidInterpolator插值器。
    • AccelerateDecelerateInterpolator:在动画开始与结束的时候速率改变比较慢,在中间的时候速率较快。
    • AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始加速。
    • AnticipateInterpolator:开始的时候向后然后向前甩。

    • AnticipateOvershootInterpolator:开始的时候向后然后向前甩一定值后返回最后的值。

    • BounceInterpolator:反弹插值器。

    • CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线。

    • DecelerateInterpolator:在动画开始的地方快然后慢。

    • LinearInterpolator:以常量速率改变。

    • OvershootInterpolator:向前甩一定值后再回到原来位置。

    • PathInterpolator:路径插值器,我们可以按照自己想要的轨迹滚动。

      PathInterpolator(Path path)
      PathInterpolator(float controlX, float controlY)
      PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)

    • FastOutLinearInInterpolator:MaterialDesign基于贝塞尔曲线的插补器效果:依次慢慢快。

    • FastOutSlowInInterpolator:基于贝塞尔曲线的插补器效果:依次慢快慢
    • LinearOutSlowInInterpolator:基于贝塞尔曲线的插补器效果:依次快慢慢

    以上的插值器运用比较广泛,在Scroller中设置一个插值器可以优化滚动的效果。

    (4)Scroller滑动辅助类的基本方法

    Scroller本身不会去滚动view,它只是一个滚动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法实现view的滚动。

    • getCurrX()

    获取mScroller当前水平滚动的位置

    • getCurrY

    获取mScroller当前竖直滚动的位置

    • getFinalX

    获取mScroller最终停止的水平位置

    • getFinalY

    获取mScroller最终停止的竖直位置

    • startScroll()

    开始滚动动画:
    startX:滚动的x方向起始点
    startY:滚动的y方向起始点
    dx:x方向的偏移量
    dy:y方向的偏移量
    duration:滚动所消耗的时间,默认为250毫秒

    startScroll(int startX, int startY, int dx, int dy)
    startScroll(int startX, int startY, int dx, int dy, int duration)
    
    • fling()

    惯性滑动,参数如下:

    startX:滚动起始点x
    startY:滚动起始点y
    velocityX:x轴方向的速度
    velocityY:y轴方向的速度
    minX:x轴最小滚动距离(注意直角坐标系)
    maxX:x轴最大滚动距离(注意直角坐标系)
    minY:y轴最小滚动距离(注意直角坐标系)
    maxY:y轴最大滚动距离(注意直角坐标系)

    • computeScrollOffset()

    判断滚动动画是否结束:
    true:滚动尚未完成
    false:滚动已经完成

    (5)Scroller实现滚动效果
    public class TestView extends View {
    
        private float mDownX = 0;
        private float mDonwY = 0;
        private float move_x = 0;
        private float move_y = 0;
        private int finalX = 0;
        private int finalY = 0;
    
        private Paint mPaint;
    
        private Scroller mScroller;
    
        public TestView(Context context) {
            super(context);
            init(context);
        }
    
        public TestView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init(context);
        }
    
        private void init(Context mContext){
            mPaint = new Paint();
            mPaint.setTextSize(80);
    
            mScroller = new Scroller(mContext);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawText("我是中国人!", 0, 100, mPaint);
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            float x = event.getX();
            float y = event.getY();
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    //标志着第一个手指按下
                    mDownX = x;//获取按下时x坐标值
                    mDonwY = y;//获取按下时y坐标值
                    break;
                case MotionEvent.ACTION_MOVE:
                    //按住一点手指开始移动
                    move_x = mDownX - x;//计算当前已经移动的x轴方向的距离
                    move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离
    
                    //开始滚动动画
                    //第一个参数:x轴开始位置
                    //第二个参数:y轴开始位置
                    //第三个参数:x轴偏移量
                    //第四个参数:y轴偏移量
                    mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 0);
                    invalidate();//目的是重绘view,是的执行computeScroll方法
                    break;
                case MotionEvent.ACTION_UP:
                    finalX = mScroller.getFinalX();
                    finalY = mScroller.getFinalY();
    
                break;
    
            }
            return true;
        }
    
        @Override
        public void computeScroll() {
            super.computeScroll();
            if(mScroller.computeScrollOffset()){//判断滚动是否完成,true说明滚动尚未完成,false说明滚动已经完成
                scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//将view直接移动到当前滚动的位置
                invalidate();//触发view重绘
            }
        }
    }
    

    效果如下:

    51.gif

    当发生ACTION_MOVE事件时,执行startScroll方法开始滚动view,由于ACTION_MOVE事件发生的特别频繁,所以startScroll方法的最后一个参数设置为0ms。

    当然,可以将startScroll方法放在ACTION_UP事件中执行,调整代码:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //标志着第一个手指按下
                mDownX = x;//获取按下时x坐标值
                mDonwY = y;//获取按下时y坐标值
                break;
            case MotionEvent.ACTION_MOVE:
                //按住一点手指开始移动
                move_x = mDownX - x;//计算当前已经移动的x轴方向的距离
                move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离
                
                break;
            case MotionEvent.ACTION_UP:
                finalX = mScroller.getFinalX();
                finalY = mScroller.getFinalY();
    
                //开始滚动动画
                //第一个参数:x轴开始位置
                //第二个参数:y轴开始位置
                //第三个参数:x轴偏移量
                //第四个参数:y轴偏移量
    
                mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 3000);
                invalidate();//目的是重绘view,是的执行computeScroll方法
    
            break;
    
        }
        return true;
    }
    

    代码的意思是:从一个坐标到另一个坐标的滑动需要3秒时间,当手指松开时开始执行滚动动画,动画时长为3秒。

    效果如下:

    52.gif
    (6)Scroller实现惯性滚动效果

    惯性滚动是指,手指松开view后,根据当前速度再滑动一段距离,就跟惯性类似。

    实现惯性滚动效果的方法是fling(),执行fling()方法的时机是MotionEvent.ACTION_UP事件,代码实现如下:

    public class TestView extends View {
    
        //惯性滑动速度追踪类
        private VelocityTracker velocityTracker;
        private float mDownX = 0;
        private float mDonwY = 0;
        private float move_x = 0;
        private float move_y = 0;
        private int finalX = 0;
        private int finalY = 0;
        private int xVelocity = 0;
        private int yVelocity = 0;
    
        private Paint mPaint;
    
        private Scroller mScroller;
        private OverScroller mOverScroller;
    
        public TestView(Context context) {
            super(context);
            init(context);
        }
    
        public TestView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init(context);
        }
    
        private void init(Context mContext){
            mPaint = new Paint();
            mPaint.setTextSize(80);
            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(50);
            mScroller = new Scroller(mContext);
            mOverScroller = new OverScroller(mContext);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawText("我是中国人!", 0, 100, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            float x = event.getX();
            float y = event.getY();
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    //标志着第一个手指按下
                    mDownX = x;//获取按下时x坐标值
                    mDonwY = y;//获取按下时y坐标值
    
                    //创建惯性滑动速度追踪类对象
                    velocityTracker = VelocityTracker.obtain();
    
    
    
                    break;
                case MotionEvent.ACTION_MOVE:
                    //按住一点手指开始移动
                    move_x = mDownX - x;//计算当前已经移动的x轴方向的距离
                    move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离
    
                    //开始滚动动画
                    //第一个参数:x轴开始位置
                    //第二个参数:y轴开始位置
                    //第三个参数:x轴偏移量
                    //第四个参数:y轴偏移量
                    if(mScroller.isFinished()){
                        mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 0);
                    }
                    invalidate();//目的是重绘view,是的执行computeScroll方法
    
    
                    //将事件加入到VelocityTracker类实例中
                    velocityTracker.addMovement(event);
                    //计算1秒内滑动的像素个数
                    velocityTracker.computeCurrentVelocity(1000);
                    //X轴方向的速度
                    xVelocity = (int) velocityTracker.getXVelocity();
                    //Y轴方向的速度
                    yVelocity = (int) velocityTracker.getYVelocity();
    
                    break;
                case MotionEvent.ACTION_UP:
    
                    //获取认为是fling的最小速率
                    int mMinimumFlingVelocity=  ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity() / 10;
    
    
                    if (Math.abs(xVelocity) >= mMinimumFlingVelocity || Math.abs(yVelocity) > mMinimumFlingVelocity) {
                        Log.d("yunchong", "触发惯性滑动");
                        mScroller.fling(getScrollX(), getScrollY(), -xVelocity, -yVelocity, -getWidth()+100, 0,  -getHeight()+100,  0);
                    } else {//缓慢滑动不处理
                    }
    
    
                    finalX = mScroller.getFinalX();
                    finalY = mScroller.getFinalY();
    
                    velocityTracker.recycle();
                    velocityTracker.clear();
                    velocityTracker = null;
    
                    break;
    
    
                case MotionEvent.ACTION_CANCEL:
    
                    velocityTracker.recycle();
                    velocityTracker.clear();
                    velocityTracker = null;
    
                    break;
    
            }
            return true;
        }
    
        @Override
        public void computeScroll() {
            super.computeScroll();
            if(mScroller.computeScrollOffset()){//判断滚动是否完成,true说明滚动尚未完成,false说明滚动已经完成
                scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//将view直接移动到当前滚动的位置
                invalidate();//触发view重绘
            }
        }
    }
    

    效果展示:

    52.gif

    [本章完...]

    相关文章

      网友评论

        本文标题:高级UI<第四十四篇>:Android Scroller详解

        本文链接:https://www.haomeiwen.com/subject/ximrfhtx.html