文章目录
- ScrollBar 属性
- 自定义圆角 ScrollBar 样式
- 源码分析
- 结尾
ScrollBar 属性
属性 | 选值 | 影响 |
scrollbars | vertical/horizontal/none | 设置垂直/水平方向的进度条 or 不设置进度条 |
scrollbarStyle | insideOverlay | 默认值,在padding区域内并且覆盖在view上 |
scrollbarStyle | insideInset | 在padding区域内并且插入在view后面 |
scrollbarStyle | outsideOverlay | 在padding区域外并且覆盖在view上 |
scrollbarStyle | outsideInset | 在padding区域外并且插入在view后面 |
scrollbarThumbVertical/Horizental | @drawable/xxx | 可滑动的位图 |
scrollbarTrackVertical/Horizental | @drawable/xxx | 进度条的背景图 |
scrollbarSize | integer | 进度条的宽度 |
fadeScrollbars | true/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:///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:///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.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.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
的高度, 而计算方式不变,这样的话,可滑动的区域不与我们设置的高度匹配,就会导致滑动块滑动不到底部的问题.
结尾
看了半天的源码, 竟然没有找到解决方法, 唉,怪自己学艺不精, 希望有解决方法的能够在评论区解惑, 万分感谢!!