RecyclerView 进度条样式

ScrollBar 属性

属性选值影响
scrollbarsvertical/horizontal/none设置垂直/水平方向的进度条 or 不设置进度条
scrollbarStyleinsideOverlay默认值,在padding区域内并且覆盖在view上
scrollbarStyleinsideInset在padding区域内并且插入在view后面
scrollbarStyleoutsideOverlay在padding区域外并且覆盖在view上
scrollbarStyleoutsideInset在padding区域外并且插入在view后面
scrollbarThumbVertical/Horizental@drawable/xxx可滑动的位图
scrollbarTrackVertical/Horizental@drawable/xxx进度条的背景图
scrollbarSizeinteger进度条的宽度
fadeScrollbarstrue/false进度条是否在只在滑动时显示

自定义圆角 ScrollBar 样式

<style name="scrollBarStyle">
        <item name="android:scrollbarThumbVertical">@drawable/scroll_bar_drawable</item>
        <item name="android:scrollbarTrackVertical">@drawable/scroll_bar_drawable_bg</item>
        <item name="android:scrollbarSize">@dimen/dpi_5px</item>
        <item name="android:overScrollMode">never</item>
        <item name="android:scrollbars">vertical</item>
        <item name="android:scrollbarStyle">insideOverlay</item>
    </style>

scroll_bar_drawable.xml

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:width="2dp"
        android:height="30dp"
        android:top="10dp">

        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="#F847F7r" />
        </shape>
    </item>

</layer-list>

注意: 如果不设置滑动块的 height 属性, 进度条的长度与RV的item数量成反比, 即: item 数量越多, 进度条长度越短;如果设置了 height , 则滑动块的高度不变

scroll_bar_drawable_bg.xml

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:width="2dp"
        android:top="10dp">

        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="#F847F7r" />
        </shape>
    </item>

</layer-list>

注意: 如果不设置进度条背景的 height 属性, 则背景长度与RV的高度一致.

固定ScrollBar的高度时,会有一个问题: 在RV滑动到底部时, 滑动块并没有到达RV的底部. 带着这个问题, 我们看一下源码, 尝试解决一下这个问题 .

源码分析

对于源码的分析,我们分两个步骤:1. scrollBar 的绘制;2. 滑动时对scrollBar的处理

  • ScrollBar 的绘制
View.java

// 初始化 Scrollbar
protected void initializeScrollbarsInternal(TypedArray a) {
    // 1. 初始化 ScrollCache, 
        initScrollCache();
    // 2. 初始化ScrollBarDrawable参数, 
        *****
    }
protected final void onDrawScrollBars(Canvas canvas) { 
    ****
    if (drawVerticalScrollBar || drawHorizontalScrollBar) {
                final ScrollBarDrawable scrollBar = cache.scrollBar;
                // 1. 绘制水平进度条
                if (drawHorizontalScrollBar) {
                    scrollBar.setParameters(computeHorizontalScrollRange(),
                            computeHorizontalScrollOffset(),
                            computeHorizontalScrollExtent(), false);
                    final Rect bounds = cache.mScrollBarBounds;
                    getHorizontalScrollBarBounds(bounds, null);
                    onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                            bounds.right, bounds.bottom);
                    if (invalidate) {
                        invalidate(bounds);
                    }
                }
                // 2. 绘制垂直进度条
                if (drawVerticalScrollBar) {
                    // 设置 scrollbar 的参数
                    scrollBar.setParameters(computeVerticalScrollRange(),
                            computeVerticalScrollOffset(),
                            computeVerticalScrollExtent(), true);
                    final Rect bounds = cache.mScrollBarBounds;
                    getVerticalScrollBarBounds(bounds, null); // 计算并获取进度条的范围
                    onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                            bounds.right, bounds.bottom); // 绘制进度条
                }
            }
}
    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
            int l, int t, int r, int b) {
        scrollBar.setBounds(l, t, r, b); 
        scrollBar.draw(canvas);
    }
LinearLayoutManager.java
    // 计算进度条滑动块的长度
    private int computeScrollExtent(RecyclerView.State state) {
        if (getChildCount() == 0) {
            return 0;
        }
        ensureLayoutState();
        return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
                findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
                findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
                this,  mSmoothScrollbarEnabled);
    }
    // 计算滑动区域
    private int computeScrollRange(RecyclerView.State state) {
        if (getChildCount() == 0) {
            return 0;
        }
        ensureLayoutState();
        return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
                findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
                findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
                this, mSmoothScrollbarEnabled);
    }
    
// 当设置 android:fadeScrollbars="true" 时, 该类会执行 滑动块的渐入渐出动画   
private static class ScrollabilityCache implements Runnable { 
    public ScrollBarDrawable scrollBar; // 绘制 scrollBar 的对象 
    public View host; 
    public void run() {
        long now = AnimationUtils.currentAnimationTimeMillis();
        if (now >= fadeStartTime) {
            ******
        }
    }
}
ScrollBarDrawable.java
public class ScrollBarDrawable extends Drawable implements Drawable.Callback { 
    @Override
    public void draw(Canvas canvas) {
        final boolean vertical = mVertical;
        final int extent = mExtent;
        final int range = mRange;
        // ****
        final Rect r = getBounds(); // 获取
        // ****
        // 1. 绘制进度条背景
        if (drawTrack) {
            drawTrack(canvas, r, vertical);
        }

        // 2. 绘制进度条滑动块
        if (drawThumb) {
            final int scrollBarLength = vertical ? r.height() : r.width();
            final int thickness = vertical ? r.width() : r.height();
            final int thumbLength =
                    ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range);
            final int thumbOffset =
                    ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range,
                            mOffset);

            drawThumb(canvas, r, thumbOffset, thumbLength, vertical);
        }
    }
}

进度条的绘制还是比较简单的, 需要注意的地方就是进度条的长度在不指定固定长度时, 是跟着item的数量变化的.

  • ScrollBar 的滑动处理

View.java

public boolean dispatchTouchEvent(MotionEvent event) {

    // 处理进度条的滑动
    handleScrollBarDragging(event)
}

protected boolean handleScrollBarDragging(MotionEvent event) { 
    final float x = event.getX();
    final float y = event.getY();
    switch (action) {
        case MotionEvent.ACTION_MOVE:
            // 处理垂直滑动
            if (mScrollCache.mScrollBarDraggingState
                        == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) {
                    final Rect bounds = mScrollCache.mScrollBarBounds;
                    getVerticalScrollBarBounds(bounds, null);
                    final int range = computeVerticalScrollRange();
                    final int offset = computeVerticalScrollOffset();
                    final int extent = computeVerticalScrollExtent();

                    final int thumbLength = ScrollBarUtils.getThumbLength(
                            bounds.height(), bounds.width(), extent, range);
                    final int thumbOffset = ScrollBarUtils.getThumbOffset(
                            bounds.height(), thumbLength, extent, range, offset);

                    final float diff = y - mScrollCache.mScrollBarDraggingPos;
                    final float maxThumbOffset = bounds.height() - thumbLength;
                    final float newThumbOffset =
                            Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
                    final int height = getHeight();
                    if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
                            && height > 0 && extent > 0) {
                        final int newY = Math.round((range - extent)
                                / ((float)extent / height) * (newThumbOffset / maxThumbOffset));
                        if (newY != getScrollY()) {
                            mScrollCache.mScrollBarDraggingPos = y;
                            setScrollY(newY);
                        }
                    }
                    return true;
                }
    }
}

其实从处理scrollbar滑动的逻辑上来看, 所有关于scrollBar的长度计算,都是很据item的数量来计算的.一旦我们固定了scrollbar
的高度, 而计算方式不变,这样的话,可滑动的区域不与我们设置的高度匹配,就会导致滑动块滑动不到底部的问题.

结尾

看了半天的源码, 竟然没有找到解决方法, 唉,怪自己学艺不精, 希望有解决方法的能够在评论区解惑, 万分感谢!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值