RecyclerView的自定义滑动条功能

概述:
RecyclerView原生的API中,是不支持可以拖动的scrollBar的。它自己带有一个仅供显示的scrollBar,并不能拖动。同时这个原生的scrollBar在item数量比较少的时候,会出现滑块滑不到底的问题。基于上述问题,我重新自己定制了RecyclerView的滑动条效果,一种是可以联动拖动的,一种是仅显示的。

以下是实现的思路和防坑事项:

1、可以联动拖动的scrollBar,其实是一个自定义的竖向的SeekBar,我们且叫它verticalSeekBar。这个竖向的SeekBar在我之前的博客有介绍过。

SeekBar的使用和竖向的SeekBar

2、首先监听RecyclerView的滚动事件,根据滚动的进度来设置scrollBar的进度。同时还要监听verticalSeekBar的滑动事件,根据滑动的进度去设置RecyclerView的内容位置。

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                Log.i(TAG,"newState : " + newState);

                //这里是为了处理数据很多的时候,列表滚动需要时间,不加控制,会让滑动条也会再次跟着一起跳动
                if(newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
                        || newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){

                    scrollFlag = newState;

                }

                super.onScrollStateChanged(recyclerView, newState);
            }
 			@Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if(scrollFlag != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL){
                    return;
                }

                int range = 0;
                int temp = recyclerView.computeVerticalScrollRange();//整体的高度,注意是整体,包括在显示区域之外的 会动态变化
                if (temp > range) {
                    range = temp;
                }
                int offset = recyclerView.computeVerticalScrollOffset();//已经向下滚动的距离,为0时表示已处于顶部。
                int extent = recyclerView.computeVerticalScrollExtent();//RecycleView显示区域的高度。
                float proportion = 1 - (float) (offset * 1.0 / (range - extent));//滑动比例
                Log.i(TAG,"proportion "+proportion);
                verticalSeekBar.setProgress((int) (proportion*100));
            }
        });
verticalSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                Log.i("VerticalSeekBar","progress:" + i + "; b:" +b);

                if(verticalSeekBar.ismIsDragging()){ //防止非手动滑动导致的进度滚动。由于是自定义的SeekBar,这里的布尔值变量始终是false。
                    int posiotn = (int) (adapter.getItemCount() * (100 -i)/100.0f);
                    int scrollSize = Math.abs(posiotn - lastpostion);
                    Log.i(TAG,"scroll size:" + Math.abs(posiotn - lastpostion));
                    lastpostion = posiotn;
                    //同样是为了处理滑动项比较多的情况
                    if(scrollSize > 50){
                        linearLayoutManager.setTimeElement(500);
                    }else {
                        linearLayoutManager.setTimeElement(2000);
                    }
                    recyclerView.smoothScrollToPosition(posiotn);
                }
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                Log.i("VerticalSeekBar","onStartTrackingTouch");
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                Log.i("VerticalSeekBar","onStopTrackingTouch");
            }
        });

3、从上面的代码段中,还涉及到一个自定义的LinearLayoutManager,这是由于我们对RecyclerView采用了smoothScrollToPosition的方法,这是一个平滑的过程。但是当数据太大的时候,平滑会比较耗时。所以我们这里需要修改一下滚动的时间。其中最关键的就是LinearLayoutManager中的一个LinearSmoothScroller类,它其中一个方法calculateTimeForScrolling(int dx)就是计算滑动这些dx需要的时间。我们可以修改如下:

 @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller linearSmoothScroller =
                new LinearSmoothScroller(recyclerView.getContext()) {
                    @Override
                    protected int calculateTimeForScrolling(int dx) {

                        Log.w("smoothScrollToPosition","dx:" + dx);
                        // 此函数计算滚动dx的距离需要多久,当要滚动的距离很大时,比如说52000,
                        // 经测试,系统会多次调用此函数,每10000距离调一次,所以总的滚动时间
                        // 是多次调用此函数返回的时间的和,所以修改每次调用该函数时返回的时间的
                        // 大小就可以影响滚动需要的总时间
                        // 这里使用限制dx的值的方式来控制时间
                           if (dx > timeElement) {
                            dx = timeElement;
                        }
                        return super.calculateTimeForScrolling(dx);
                    }
                };
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

注意点:
1、 如果想要在代码中去主动设置VerticalSeekBar的Thumb,那么在设置了之后一定要紧接着设置一次ThumbOffset属性。不然就会出现滑块图标在滑动到底部时,被遮挡住一部分显示。

/**
     * 设置滑动条的类型
     * @param type
     */
    public void setScrollThumbType(int type){
        Drawable thumb = null;
        if(type == THUMB_TYPE_DIAL){
            thumb = getResources().getDrawable(R.mipmap.dial_scroll_thumb);
            verticalSeekBar.setThumb(thumb);
        }else if(type == THUMB_TYPE_CONTACT){
            thumb = getResources().getDrawable(R.mipmap.contact_scroll_thumb);
            verticalSeekBar.setThumb(thumb);
        }
        verticalSeekBar.setThumbOffset(0);
    }

2、在XML中引用VerticalSeekBar的时候,一定要设置它的背景图,就算是透明的背景,也要去设置一下,不然就会出现非常神奇的事情,在拖动VerticalSeekBar的时候右上脚会出现一个移动的圆点。

gitbub地址:RecyclerView的滑动条效果demo

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值