SoFunction
Updated on 2025-04-13

Two ways to implement Android custom Scrollbar

This article introduces two methods to implement custom scroll bars, respectively, through the ItemDecoration scheme and the independent View scheme to realize scroll bar customization. Both solutions support the following core functions:

  • Supports custom right-side spacing
  • Supports holding up and down drag
  • Zoom in 1.5 times when pressed and held
  • Automatically hide and display logic
  • Smooth animation effects

Solution 1: ItemDecoration implementation (recommended for RecyclerView)

Implementation principle

Through inheritance,existonDrawOverDraw scroll bars in the middle, and combine touch event processing to achieve interaction

Complete code implementation

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

public class ScrollBarItemDecoration extends  {
    // Size configuration (unit: dp)    private static final int DEFAULT_THUMB_WIDTH = 8;
    private static final int DEFAULT_MIN_LENGTH = 20;
    private static final int DEFAULT_RIGHT_MARGIN = 20;
    private static final float SCALE_FACTOR = 1.5f;
    
    // Color configuration    private static final int DEFAULT_THUMB_COLOR = 0xFF888888;
    private static final int DEFAULT_TRACK_COLOR = 0xFFEEEEEE;

    // Drawing tool    private final Paint thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint trackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Rect thumbRect = new Rect();
    private final Rect trackRect = new Rect();

    // Status control    private float scrollRange;
    private boolean isDragging;
    private float thumbScale = 1f;
    private RecyclerView recyclerView;

    public ScrollBarItemDecoration(Context context) {
        // Size conversion        int thumbWidth = dpToPx(context, DEFAULT_THUMB_WIDTH);
        int rightMargin = dpToPx(context, DEFAULT_RIGHT_MARGIN);
        
        // Initialization of brushes        (DEFAULT_THUMB_COLOR);
        (DEFAULT_TRACK_COLOR);
    }

    public void attachToRecyclerView(RecyclerView recyclerView) {
         = recyclerView;
        (this);
        
        // Scroll monitoring        (new () {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                updateScrollParams();
                ();
            }
        });

        // Touch event handling        (new () {
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                handleTouch(e);
                return false;
            }
        });
    }

    private void updateScrollParams() {
        int totalHeight = ();
        int visibleHeight = ();
        scrollRange = totalHeight - visibleHeight;
    }

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull  state) {
        // Draw tracks        (() - thumbWidth - rightMargin, 0, 
                    () - rightMargin, ());
        (trackRect, trackPaint);

        // Calculate the slider position        float thumbPosition = (() / scrollRange) * 
                            (() - thumbLength);
        int scaledWidth = (int)(thumbWidth * thumbScale);
        
        // Draw slider        (() - scaledWidth - rightMargin, (int)thumbPosition,
                    () - rightMargin, (int)(thumbPosition + thumbLength));
        (thumbRect, thumbPaint);
    }

    private void handleTouch(MotionEvent e) {
        switch (()) {
            case MotionEvent.ACTION_DOWN:
                if (((), ())) {
                    isDragging = true;
                    thumbScale = SCALE_FACTOR;
                    ();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isDragging) {
                    float newOffset = (() / ()) * scrollRange;
                    ((int)newOffset);
                }
                break;
            case MotionEvent.ACTION_UP:
                isDragging = false;
                thumbScale = 1f;
                ();
                break;
        }
    }

    private int dpToPx(Context context, int dp) {
        return (int)(dp * ().getDisplayMetrics().density + 0.5f);
    }
}

Example of usage

<!-- activity_main.xml -->
<
    android:
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
// 
public class MainActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        RecyclerView recyclerView = findViewById();
        new ScrollBarItemDecoration(this).attachToRecyclerView(recyclerView);
        // Set up Adapter and other subsequent operations...    }
}

Advantages and limitations

advantage:

  • Deep integration with RecyclerView
  • Low memory footprint
  • No need to modify the layout structure

Limitations:

  • Available for RecyclerView only
  • Complex gesture processing requires additional development

Solution 2: Independent View implementation (supports arbitrary scroll view)

Implementation principle

Scrollbar is implemented through custom View, and can be adapted to RecyclerView/NestedScrollView and other scroll containers

public class CustomScrollBarView extends View {
    // Draw parameters    private final Paint thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final RectF thumbRect = new RectF();
    
    // Status control    private float scrollRange;
    private boolean isDragging;
    private ValueAnimator widthAnimator;

    public CustomScrollBarView(Context context) {
        super(context);
        (0xCCCCCC);
    }

    public void attachToView(View scrollView) {
        if (scrollView instanceof RecyclerView) {
            ((RecyclerView)scrollView).addOnScrollListener(new () {
                @Override
                public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
                    updateScrollParams(rv);
                }
            });
        } else if (scrollView instanceof NestedScrollView) {
            ((NestedScrollView)scrollView).setOnScrollChangeListener((v, x, y, oldX, oldY) -&gt; {
                updateScrollParams(v);
            });
        }
        
        setOnTouchListener((v, event) -&gt; {
            handleTouch(event);
            return true;
        });
    }

    private void updateScrollParams(View scrollView) {
        int totalHeight = ();
        int visibleHeight = ();
        scrollRange = totalHeight - visibleHeight;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float thumbPos = (scrollOffset / scrollRange) * (getHeight() - thumbLength);
        (getWidth()-thumbWidth, thumbPos, getWidth(), thumbPos+thumbLength);
        (thumbRect, 20, 20, thumbPaint);
    }

    private void handleTouch(MotionEvent event) {
        switch (()) {
            case MotionEvent.ACTION_DOWN:
                if (((), ())) {
                    startWidthAnimation(thumbWidth, (int)(thumbWidth*1.5f));
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isDragging) {
                    float deltaY = () - lastTouchY;
                    (0, (int)(deltaY * 3.5f));
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                startWidthAnimation(thumbWidthWhenDragging, thumbWidth);
                break;
        }
    }

    private void startWidthAnimation(int from, int to) {
        if (widthAnimator != null) ();
        widthAnimator = (from, to);
        (anim -&gt; {
            thumbWidth = (int)();
            invalidate();
        });
        ();
    }
}

Example of usage

&lt;!-- Layout file --&gt;
&lt;FrameLayout&gt;
    &lt;
        android:
        android:layout_width="match_parent"
        android:layout_height="match_parent"/&gt;
        
    &lt;
        android:layout_width="8dp"
        android:layout_height="match_parent"
        android:layout_gravity="right"/&gt;
&lt;/FrameLayout&gt;

Advantages and limitations

advantage:

  • Supports any scrolling view
  • More animation effects
  • Higher customization freedom

Limitations:

  • Need to manually maintain the layout position
  • A little high memory usage

Solution comparison

characteristic ItemDecoration Solution Independent View Solution
Integration difficulty ★★☆☆☆ ★★★☆☆
Performance ★★★★☆ ★★★☆☆
Functional scalability ★★☆☆☆ ★★★★★
Multi-container support RecyclerView only All scroll views
Animation effect support Basic scaling Supports complex animations

Best Practice Recommendations

  • RecyclerView dedicated scenarioItemDecoration solution is recommended for better performance and memory efficiency

  • Complex interaction requirementsWhen the following functions are required, it is recommended to adopt an independent View solution:

    • Unified scrollbar across view types
    • Complex gesture recognition (such as double-click operation)
    • Multi-step animation effect
    • Non-vertical scrolling support
  • Performance optimization suggestions

    • Avoid creating objects in draw methods
    • Use ValueAnimator instead of ObjectAnimator
    • For long lists, enable setHasFixedSize of RecyclerView
  • Visual customization tips

// Modify the scroll bar style();
();
(12); // Unit: dp

Frequently Asked Questions

Q1 Is the scroll bar displayed incorrectly?

  • Check the clipToPadding property of the parent container
  • Confirm the scroll bar width calculation contains margin value

Q2 Is there a stutter while dragging?

  • Ensure that time-consuming operations are not performed on the UI thread
  • Reduce the frequency of the scroll event triggering
  • Accelerate layers with hardware

Q3 Conflict with pull-down refresh?

// Add touch interception judgment in CoordinatorLayout@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    if (isDragging) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return true;
    }
    return (e);
}

Through the comparison of the two solutions, the ItemDecoration solution is suitable for lightweight customization of RecyclerView, while the independent View solution provides greater flexibility and scalability.

The above is the detailed content of the two implementation methods of Android custom Scrollbar. For more information about Android custom Scrollbar, please follow my other related articles!