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.