[JAVA] Correct the stop position of smoothScrollToPosition of RecyclerView

Introduction

When I used smoothScrollToPosition with RecyclerView, it stopped only at a halfway position, so it is a memo when I checked it.

Premise

Use LinearLayoutManager The scroll direction is vertical

What I wanted to do

I want to make sure that the scroll stop position is at the top.

Default behavior

Depending on the scrolling direction, it may go up or down ...

Internal implementation investigation

smoothScrollToPosition The implementation in the commonly used LinearLayoutManager looks like this.

LinearLayoutManager.java


@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
   LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext());
   //Set scroll targetPosition in scroller
   linearSmoothScroller.setTargetPosition(position);
   //Scroll using scroller
   startSmoothScroll(linearSmoothScroller);
}

LinearSmoothScroller inherits from SmoothScroller.

SmoothScroller

SmoothScroller is an abstract class with a method called ʻonTargetFound` in it.

SmoothScroller.java


public abstract static class SmoothScroller {
   ...(Abbreviation)
   /**
    * Called when the target position is laid out. This is the last callback SmoothScroller
    * will receive and it should update the provided {@link Action} to define the scroll
    * details towards the target view.
    * @param targetView    The view element which render the target position.
    * @param state         Transient state of RecyclerView
    * @param action        Action instance that you should update to define final scroll action
    *                      towards the targetView
    */
   protected abstract void onTargetFound(View targetView, State state, Action action);
   ...(Abbreviation)
}

SmoothScroller has some callbacks, but this callback is the last one to be notified and will be called when the target View comes into the layout. As for what to do here, there is a comment to the effect that update the ʻactionpassed as an argument and fine-tune the position. Now, let's check what kind of implementation is specifically inLinearSmoothScroller`.

LinearSmoothScroller

LinearSmoothScroller.java


@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
    final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
    final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
    final int distance = (int) Math.sqrt(dx * dx + dy * dy);
    final int time = calculateTimeForDeceleration(distance);
    if (time > 0) {
        action.update(-dx, -dy, time, mDecelerateInterpolator);
    }
}

It calculates the required scroll amount in the x and y directions and executes ʻaction.update. The content of the implementation of calculateDx / DyToMakeVisible` to be calculated is muddy, so if you look only at the comments, it looks like this.

LinearSmoothScroller.java


/**
 * Calculates the vertical scroll amount necessary to make the given view fully visible
 * inside the RecyclerView.
 *
 * @param view           The view which we want to make fully visible
 * @param snapPreference The edge which the view should snap to when entering the visible
 *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
 *                       {@link #SNAP_TO_ANY}.
 * @return The vertical scroll amount necessary to make the view visible with the given
 * snap preference.
 */
public int calculateDyToMakeVisible(View view, int snapPreference) {

It says that it will calculate the amount of scrolling required before the entire view of the first argument can be seen. The second argument indicates the position where you want the view to be finally displayed, and select from the following three.

Looking at the getVerticalSnapPreference that determines the second argument, it looks like this.

LinearSmoothScroller.java


protected int getVerticalSnapPreference() {
    return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
            mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
}

It switches between SNAP_TO_START and SNAP_TO_END depending on the direction of sucrose. In other words, for this purpose, it can be achieved by modifying (override) this snapPreference.

Conclusion

The description has become long, but I've found that the code that should actually be implemented needs only a slight extension of the default LinearSmoothScroller.

CustomLayoutManager.kt


class CustomLayoutManager(context: Context) : LinearLayoutManager(context) {
    override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?, position: Int) {
        val linearSmoothScroller = object : LinearSmoothScroller(recyclerView.context) {
            override fun getVerticalSnapPreference(): Int = if (reverseLayout) SNAP_TO_END else SNAP_TO_START
        }
        linearSmoothScroller.targetPosition = position
        startSmoothScroll(linearSmoothScroller)
    }
}

By the way, in this example, only reverseLayout is considered, but I think that it is general-purpose to implement it considering the scroll direction (vertical / horizontal) as well.

Recommended Posts

Correct the stop position of smoothScrollToPosition of RecyclerView
Now, I've summarized the basics of RecyclerView
Folding and unfolding the contents of the Recyclerview
Customize how to divide the contents of Recyclerview
[SwiftUI] How to specify the abbreviated position of Text
How is the next value of the Time object correct?
I want to return the scroll position of UITableView!
The world of clara-rules (2)
Judgment of the calendar
The world of clara-rules (4)
The world of clara-rules (1)
The world of clara-rules (3)
The world of clara-rules (5)
The idea of quicksort
The idea of jQuery
The correct answer for money! The correct answer for silver! [Japanese version of sequence]