Scroller

作者: _Rice_ | 来源:发表于2019-12-31 13:47 被阅读0次

    在Android开发中有多种方式实现View的滑动,常见的有三种如下:

    • 不断地修改View的LayoutParams
    • 采用动画向View施加位移效果:主要通过改变View的translationX和translationY参数来实现。
    • 调用View的scrollTo( )、scrollBy( )

    其实,在Android中我们常见到的ListView、Launcher、SlidingMenu、ViewPager等等这些具有弹性滑动的View的背后都隐藏着一个机智又乖巧的小精灵——Scroller。

    scrollTo( )和scrollBy( )

    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }
    
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
    
    

    小结:

    • mScrollX和mScrollY分别表示View在X、Y方向的滚动距离
    • scrollTo( )表示View相对于其初始位置滚动某段距离。
    • 由于View的初始位置是不变的,所以如果利用相同输入参数多次调用scrollTo()方法,View只会出现一次滚动的效果而不是多次。
    • scrollBy( )表示在mScrollX和mScrollY的基础上继续滚动。

    两者区别:scrollBy是内部调用了scrollTo的,它是基于当前位置的相对滑动;而scrollTo是绝对滑动两者区别:scrollBy是内部调用了scrollTo的,它是基于当前位置的相对滑动;而scrollTo是绝对滑动

    Scroller的使用

    其实,在实际的开发中我们真正地使用scrollTo()和scrollBy()来实现View的滑动的时候并不多。因为这两个方法产生的滑动是不连贯的,跳跃的,闪烁的,最终的效果也不够平滑。所以,我们多采用系统提供的工具类Scroller来实现View的滚动效果。

    与scrollTo/scrollBy不同:scrollTo/scrollBy过程是瞬间完成的,非平滑;而Scroller则有过渡滑动的效果。
    注意:Scoller本身无法让View弹性滑动,它需要和View的computerScroller方法配合使用。

    Scroller的使用概括为以下五个主要步骤:

    • 初始化Scroller
    • 调用Scroll()开始滚动
    • 执行invalidate()刷新界面
    • 重写View的computeScroll()并在其内部实现与滚动相关的业务逻辑
    • 再次执行invalidate()刷新界面

    流程图如下:


    image.png

    public void startScroll(int startX, int startY, int dx, int dy, int duration)
    函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为 (startX+dx , startY+dy)处。

    computeScroll()
    空方法,由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制 。其调用过程位于View绘制流程draw()过程中
    根据当前已经逝去的时间,获取当前应该偏移的坐标

    computeScrollOffset()
    Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,

    Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:

    • 创建Scroller的实例
    • 调用startScroll()方法来初始化滚动数据并刷新界面
    • 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
    例子实现:
    public class LauncherViewGroup extends ViewGroup {
        private int width;
        private int scrollX;
        private float downX;
        private float moveX;
        private float lastX;
        private int touchSlop;
        private int leftLimit;
        private int rightLimit;
        private Context mContext;
        private Scroller mScroller;
        private final String TAG = "stay4it";
    
        public LauncherViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
            mContext=context;
            width = getResources().getDisplayMetrics().widthPixels;
            mScroller = new Scroller(mContext);
            ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext);
            touchSlop = viewConfiguration.getScaledTouchSlop();
        }
    
        @Override
        public void computeScroll() {
    //这里获取到的mCurrX、mCurrY就是系统依据startScroll()中设置的时间,距离,Interpolator计算出了当前View的滚动数据。 
            if (mScroller.computeScrollOffset()) {
                int x=mScroller.getCurrX();
                int y=mScroller.getCurrY();
                scrollTo(x, y);
                invalidate();
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (changed) {
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View childView = getChildAt(i);
                    int left = i * childView.getMeasuredWidth();
                    int top = 0;
                    int right = (i + 1) * childView.getMeasuredWidth();
                    int bottom = childView.getMeasuredHeight();
                    childView.layout(left, top, right, bottom);
                }
                leftLimit = getChildAt(0).getLeft();
                rightLimit = getChildAt(childCount - 1).getRight();
            }
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = ev.getRawX();
                    lastX = downX;
                    break;
                case MotionEvent.ACTION_MOVE:
                    moveX = ev.getRawX();
                    float moveDistance = Math.abs(moveX - lastX);
                    lastX = moveX;
                    if (moveDistance > touchSlop) {
                        return true;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return super.onInterceptTouchEvent(ev);
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    moveX = ev.getRawX();
                    int moveDistanceX = (int) (lastX - moveX);
                    scrollX = getScrollX();
                    if (scrollX + moveDistanceX < leftLimit) {
                        scrollTo(leftLimit, 0);
                        return true;
                    }
    
                    if (scrollX + moveDistanceX + width > rightLimit) {
                        scrollTo(rightLimit - width, 0);
                        return true;
                    }
                    scrollBy(moveDistanceX, 0);
                    lastX = moveX;
                    break;
                case MotionEvent.ACTION_UP:
                    scrollX = getScrollX();
                    int index = (scrollX + width / 2) / width;
                    int distanceX = width * index - scrollX;
                    mScroller.startScroll(scrollX, 0, distanceX, 0);
                    invalidate();
                    break;
            }
            return super.onTouchEvent(ev);
        }
    }
    
    

    参考:
    https://blog.csdn.net/guolin_blog/article/details/48719871
    https://blog.csdn.net/lfdfhl/article/details/53143114
    https://www.jianshu.com/p/859592b43d38
    https://www.jianshu.com/p/06ff0dfeed39
    https://www.cnblogs.com/wanqieddy/archive/2012/05/05/2484534.html

    相关文章

      网友评论

          本文标题:Scroller

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