preface

Recently encountered a demand in the development, need RecyclerView to the specified position after the top display, when encountered this problem, the first reaction is to directly use RecyclerView smoothScrollToPosition() method, to achieve the corresponding position of the smooth rolling. But in the actual use, I found that there is no exactly what I want. This thinking lazy directly from the net Copy, but found that the effect is not very good. So they went to study the source code.

This series is divided into two articles.

  • If you want to solve scrolling to the top via smoothScrollToPosition, or scrolling to accelerate, check out this article,
  • If you want to understand its internal implementation, see RecyclerView. SmoothScrollToPosition understanding once

Attention!! Attention!! Attention!! This is the linear layer outManager that is vertically linear. The horizontal thinking is the same, but the modification method is different. You must pay attention to the prerequisites.

How to scroll to the top using smoothScrollToPosition?

If you look at my another article RecyclerView. SmoothScrollToPosition understanding once, it should be clear, in fact, after you have set the target location, when find the target view, The last way to make RecyclerView scroll is the calculateDtToFit() method of the LinearSmoothScroller in the LinearLayoutManager.

 public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
            snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
                if (dtStart > 0) {
                    return dtStart;
                }
                final int dtEnd = boxEnd - viewEnd;
                if (dtEnd < 0) {
                    return dtEnd;
                }
                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
        return 0;
    }

Copy the code

That is, if LinerlayoutManager is vertical, and snapPreference defaults to SNAP_ANY, then we get the following three cases.

  • When the rolling position is within the visible range, the rolling distance is 0, so it will not roll.
  • Content scrolls up and only up to the top when the scroll position is before the visible range.
  • Content is scrolled down when the scrolling position is outside the visible distance, and only to the bottom.

SnapPreference value is at the same time through the getVerticalSnapPreference LinearSmoothScroller () and getHorizontalSnapPreference () to set.

So to make the scroll position corresponding to the target view displayed at the top, we create a new class and inherit the LinearLayoutManager. At the same time create TopSnappedSmoothScroller inheritance LinearSmoothScroller, rewrite its getVerticalSnapPreference () method. (if you are horizontal, please modify getHorizontalSnapPreference method)

public class LinearLayoutManagerWithScrollTop extends LinearLayoutManager {

    public LinearLayoutManagerWithScrollTop(Context context) {
        super(context);
    }

    public LinearLayoutManagerWithScrollTop(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LinearLayoutManagerWithScrollTop(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        TopSnappedSmoothScroller topSnappedSmoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
        topSnappedSmoothScroller.setTargetPosition(position);
        startSmoothScroll(topSnappedSmoothScroller);
    }

    class TopSnappedSmoothScroller extends LinearSmoothScroller {

        public TopSnappedSmoothScroller(Context context) {
            super(context);
        }

        @Nullable
        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return LinearLayoutManagerWithScrollTop.this.computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference(a) {
            return SNAP_TO_START;// Set the scroll position}}}Copy the code

After creating this class, we simply set up the corresponding new layout manager for RecyclerView and call the smoothScrollToPosition() method.

How to set the scrolling speed of smoothScrollToPosition?

In fact, in RecyclerView, scrolling to the specified position is divided into two parts. The first is the speed before the corresponding view of the target position is not found, and the first is the speed of scrolling after the corresponding view of the target position is found.

Before we find our target location

 action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);

Copy the code

The default starting distance when starting to find the target location is 12000 (unit :px). Note here that we use LinearInterpolator, which means that our RecyclerView speed is constant until the target location is found.

Once we have our target location

action.update(-dx, -dy, time, mDecelerateInterpolator);

Copy the code

Activity. Here we use Activity activity. In other words, after finding the target location, RecyclerView is the speed is slowly reduced.

So here is an idea that we can modify the two parts of the interpolator to change the scrolling speed of RecyclerView. Of course, I did not give the example code here, because I found that Google did not want us to modify the interpolator, because in its LinearSmoothScroller, He made both interpolators protected. (So I think it is not elegant to change it like this.) If you are interested in it, you can modify it.

So how do you change the speed now?

Since it is more troublesome to modify the interpolator, we can modify the scrolling time!!!!!! Hopefully you’ll remember that when we call our Action update method, we not only save how far the RecyclerView needs to scroll, we also save the total amount of time it takes to scroll.

Sliding time is through calculateTimeForScrolling () the method to calculate.

    protected int calculateTimeForScrolling(int dx) {
        // The time is rounded.
        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
    }

Copy the code

The MILLISECONDS_PER_PX is created when the LinearSmoothScroller is initialized.

public LinearSmoothScroller(Context context) {
      MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
    }
Copy the code

Look at the calculateSpeedPerPixel() method

    private static final float MILLISECONDS_PER_INCH = 25f;// By default, it takes 25ms to move an inch
    //
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
    }
Copy the code

In other words, the current scrolling speed is related to the pixel density of the screen. By taking the pixel density per inch of the current phone screen, and the time it takes to move one pixel density, you can calculate the time it takes to move one pixel density by dividing the time it takes to move one inch by the pixel density.

So now, there are two ways to change the scrolling speed of RecyclerView, or we can change the calculateSpeedPerPixel method to change the amount of time it takes to move a pixel. Either we modify calculateTimeForScrolling method.

Here I use the change calculateSpeedPerPixel method to change the speed. I’m going to change it to take 10ms to move an inch, so that means you’re rolling faster. Then the corresponding scrolling time is smaller

  protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {   
      return 10f / displayMetrics.densityDpi;                                                      
  }               
                                                            
Copy the code

At this point, I’m sure you’ve figured out how to change the speed and the scroll position. All right, all right. I’m too sleepy to sleep.

By the way, the source code is here. If you’re interested, you can go and study it.

The last

Finally, I’ve attached a project I wrote based on Kotlin’s SimpleEyes(PS: Actually, a lot of kids have started to copy this app before me, but I think it’s a good thing to do. So my project should be different from everyone else’s, not just a simple application. But, but. But. Say three important things. Still in development stage, don’t hit me), welcome everyone follow and start.