Recently, there is a demand that its scrollbar style is similar to that of Taobao Channel bar, and the style of the scrollbar needs to be modified, so the scrollbar of View is summarized.

The properties of scrollbar are as follows:

attribute describe
android:scrollbars Whether scroll bars are displayed. Horizontal: indicates the horizontal direction. Vertical: indicates the vertical direction
android:scrollbarStyle Scrollbar styles and styles. InsideOverlay: Default value for overlay within the padding area and on top of the content. InsideInset: inside the padding area and inserted after the view. OutsideOverlay: Overlay outside the padding area and on top of the view; OutsideInset: indicates that it is outside the padding area and inserted after the view.
android:scrollbarSize Sets the width of the scroll bar.
android:scrollbarThumbHorizontal[Vertical] Set horizontal/vertical scroll bar (slider) styles, @drawable or @color.
android:scrollbarTrackHorizontal[Vertical] Set horizontal/vertical scrollbar background styles, @drawable or @color.
android:fadeScrollbars Whether to hide the scroll bar without scrolling. Default: true

Because to implement the scrollbar style shown below,

Because the requirement is achieved by RecyclerView, so in RecyclerView by setting scrollbarThumbHorizontal and scrollbarTrackHorizontal two properties to achieve custom scrollbar style.

android:scrollbarThumbHorizontal="@drawable/scrollbar_thumb"
android:scrollbarTrackHorizontal="@drawable/scrollbar_track"
Copy the code

Scrollbar style scrollbar_thumb.xml

<? The XML version = "1.0" encoding = "utf-8"? > <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:width="20dp" Android :left="170dp"> <shape> <solid Android :color="#FF97AF" /> <corners android:radius="1.5dp" /> </shape> </item> </layer-list>Copy the code

Scrollbar background style scrollbar_track.xml

<? The XML version = "1.0" encoding = "utf-8"? > <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:width="60dp" Android :left="170dp"> <shape> <solid Android :color="#FFEDF2" /> <corners android:radius="1.5dp" /> </shape> </item> </layer-list>Copy the code

Limiting the size and position of the scrollbar and background with with and left looks like this:

The effect looks good, but when you slide the scrollbar out of the background, as shown below:

Obviously the actual scroll range of the scroll bar has not changed, so you need to set the actual scroll range of the scroll bar. By looking at the code in View, the method onDrawScrollBars to achieve the drawing of scrollbar, where the key code is:

final ScrollBarDrawable scrollBar = cache.scrollBar; 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); }}Copy the code

Used to compute bounds of getHorizontalScrollBarBounds values of left and right, the key code is as follows:

final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden();
final int size = getHorizontalScrollbarHeight();
final int verticalScrollBarGap = drawVerticalScrollBar ? getVerticalScrollbarWidth() : 0;
final int width = mRight - mLeft;
final int height = mBottom - mTop;
bounds.top = mScrollY + height - size - (mUserPaddingBottom & inside);
bounds.left = mScrollX + (mPaddingLeft & inside);
bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
bounds.bottom = bounds.top + size;
Copy the code

You can see that the left and right values of bounds are calculated from the actual width of the View. Then through onDrawHorizontalScrollBar, bounds value into the scrollBar

protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
            int l, int t, int r, int b) {
    scrollBar.setBounds(l, t, r, b);
    scrollBar.draw(canvas);
}
Copy the code

It can be seen that the final calculation of srCollbar is achieved in ScrollBarDrawable, and the draw method in ScrollBarDrawable is used to draw the scrollbar and background. The key codes are as follows

final Rect r = getBounds();
if (drawTrack) {
    drawTrack(canvas, r, vertical);
}

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);
}
Copy the code

ThumbLength and thumbOffset are used to control the length and moving position of the slider in ScrollBarUtils

public static int getThumbLength(int size, int thickness, int extent, int range) {
    // Avoid the tiny thumb.
    final int minLength = thickness * 2;
    int length = Math.round((float) size * extent / range);
    if (length < minLength) {
        length = minLength;
    }
    return length;
}

public static int getThumbOffset(int size, int thumbLength, int extent, int range, int offset) {
    // Avoid the too-big thumb.
    int thumbOffset = Math.round((float) (size - thumbLength) * offset / (range - extent));
    if (thumbOffset > size - thumbLength) {
        thumbOffset = size - thumbLength;
    }
    return thumbOffset;
}
Copy the code

Length of them by size, among other, three calculation range, thumbOffset is through size, thumbLength, offset, range, among several calculation, so the scroll bar slider sliding range lies in these two methods; Using the draw method in ScrollBarDrawable, size is with bounds. Therefore, you can change the slider’s sliding range as long as range is the length of the View, extent is the desired slider width, and offset is the desired offset of the custom slider. Range,offset, and extent are set by setParameters method of ScrollBarDrawable. Namely computeHorizontalScrollRange (), computeHorizontalScrollOffset (), computeHorizontalScrollExtent (), So you can set it just by using these three compute values. And by viewing RecyclerView related code

/** * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll  bars, override * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your * LayoutManager.</p> * * @return The total horizontal range represented by the vertical scrollbar * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) */ @Override public int computeHorizontalScrollRange() { if (mLayout == null) { return 0; } return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; }Copy the code

Known computeHorizontalScrollRange (), computeHorizontalScrollOffset (), computeHorizontalScrollExtent () three values by LayoutManager calculation, The LinearLayoutManager is used to calculate the length of all items and the range of movement. Thus, the movement calculation of the scroll bar is calculated according to the movement ratio of all Item items in RecyclerView, so just define the LinearLayoutManager and modify it as follows:

public class CustomLinearLayoutManager extends LinearLayoutManager { private int viewW; // recyclerView width private int trackW; // Scroll bar background width private int thumbW; / / scroll bar thumb wide public ScrollbarLinearLayoutManager Context (Context) {super (Context); } public ScrollbarLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public ScrollbarLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void setParams(int viewW, int trackW, int thumbW) { this.viewW = viewW; this.trackW = trackW; this.thumbW = thumbW; } @Override public int computeHorizontalScrollOffset(RecyclerView.State state) { return isCustomScrollbar() ? (int) ((float) super.computeHorizontalScrollOffset(state) / (super .computeHorizontalScrollRange(state) - super.computeHorizontalScrollExtent(state)) * (trackW - thumbW)) : super.computeHorizontalScrollOffset(state); } @Override public int computeHorizontalScrollExtent(RecyclerView.State state) { return isCustomScrollbar() ? thumbW : super.computeHorizontalScrollExtent(state); } @Override public int computeHorizontalScrollRange(RecyclerView.State state) { return isCustomScrollbar() ? viewW : super.computeHorizontalScrollRange(state); } private boolean isCustomScrollbar() { return viewW > 0 && trackW > 0 && thumbW > 0 && viewW > trackW && trackW > thumbW; }}Copy the code

Namely computeHorizontalScrollRange results for RecyclerView wide, computeHorizontalScrollExtent for actual slider wide, ComputeHorizontalScrollOffset for super. Offset/(super. Range – super. Among other) times (trackW – thumbW), namely the actual Offset value, the result as follow, The scrollbar slider is restricted to sliding within the actual background.