ViewPager2通过反射可以获取到RecyclerView,也可以使用。
效果如图:
参考代码:
// 下面代码参考 https://github.com/LateNightMoon/ScrollbarSample
public class VerticalScrollbar extends View {
private static final String TAG = "VerticalScrollbar";
private static final int START = 0x1;
private static final int END = START << 1;
private static final int SCROLLING = START << 2;
private static final long DELAY_CLEAR = 1000L;
private int mScrollState = START;
private final Rect trackRect = new Rect();
private Drawable trackBackground = null;
private Drawable thumbBackground = ResourcesCompat.getDrawable(getResources(), R.drawable.scroller, null);
private float thumbScale = 0f;
private float scrollScale = 0f;
private RecyclerView mRecyclerView;
private volatile boolean isCleared = false;
private int leastCount = 4;
private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
removeCallbacks(clearCanvasRunner);
isCleared = false;
computeScrollScale();
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
removeCallbacks(clearCanvasRunner);
postDelayed(clearCanvasRunner, DELAY_CLEAR);
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
computeScrollScale();
postInvalidate();
}
};
public VerticalScrollbar(Context context) {
this(context, null);
}
public VerticalScrollbar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalScrollbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(LAYER_TYPE_HARDWARE, null);
}
public void bindRecyclerView(@Nullable RecyclerView recyclerView) {
if (mRecyclerView == recyclerView) {
return;
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
setCallbacks();
}
}
private void setCallbacks() {
mRecyclerView.addOnScrollListener(mScrollListener);
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
computeScrollScale();
}
});
computeScrollScale();
}
public void computeScrollScale() {
if (mRecyclerView == null) {
return;
}
float scrollExtent = mRecyclerView.computeVerticalScrollExtent();
float scrollRange = mRecyclerView.computeVerticalScrollRange();
if (scrollRange != 0 && scrollExtent != 0) {
thumbScale = scrollExtent / scrollRange;
}
float offset = mRecyclerView.computeVerticalScrollOffset();
if (offset != 0 && scrollRange != 0) {
scrollScale = offset / scrollRange;
}
float toEndExtent = scrollRange - scrollExtent;
if (offset == 0) {
mScrollState = START;
} else if (BigDecimal.valueOf(toEndExtent).equals(BigDecimal.valueOf(offset))) {
mScrollState = END;
} else {
mScrollState = SCROLLING;
}
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureHandler(widthMeasureSpec), measureHandler(heightMeasureSpec));
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == VISIBLE) {
isCleared = false;
invalidate();
removeCallbacks(clearCanvasRunner);
postDelayed(clearCanvasRunner, DELAY_CLEAR);
}
}
private int measureHandler(int measureSpec) {
int result = 0;
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else if (mode == MeasureSpec.AT_MOST) {
result = Math.max(size, result);
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isCleared || !hasContent()) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (trackBackground != null) {
trackRect.set(0, 0, width, height);
trackBackground.setBounds(trackRect);
trackBackground.draw(canvas);
}
int thumbTop = (int) (scrollScale * height);
int thumbBottom = (int) (thumbTop + height * thumbScale);
// 添加 state 是修复滚动计算差
if (thumbBackground != null) {
switch (mScrollState) {
case START:
trackRect.set(0, 0, width, thumbBottom);
break;
case SCROLLING:
trackRect.set(0, thumbTop, width, thumbBottom);
break;
case END:
trackRect.set(0, thumbTop, width, height);
break;
default:
}
thumbBackground.setBounds(trackRect);
thumbBackground.draw(canvas);
}
}
private final Runnable clearCanvasRunner = () -> {
isCleared = true;
invalidate();
};
@SuppressWarnings("rawtypes")
private boolean hasContent() {
if (mRecyclerView == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
int itemCount = adapter.getItemCount();
return itemCount > leastCount;
}
public void setLeastCount(int leastCount) {
this.leastCount = leastCount;
}
}