The last article showed you how to scroll through a list with a finger swipe. How does a list scroll a short distance away from its hands? Go check the source code to find out.

Is offhand scrolling performed immediately?

Write a simple list demo, using Profiler to record the complete call chain in the process of off-hand scrolling (about how to use Profiler to find the key path of source code execution can click RecyclerView scrolling is how to achieve? (a) | unlock reading source new position) :

After sell trigger RecyclerView. Fling (), it calls the point in RecyclerView. OnTouchEvent () :

public class RecyclerView {
    @Override
    public boolean onTouchEvent(MotionEvent e) {...final MotionEvent vtev = MotionEvent.obtain(e);
        switch (action) {
            case MotionEvent.ACTION_UP: {
                mVelocityTracker.addMovement(vtev);
                // Calculate the rate
                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                final float xvel = canScrollHorizontally ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
                final float yvel = canScrollVertically ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
                / / trigger a fling
                if(! ((xvel ! =0|| yvel ! =0) && fling((int) xvel, (int) yvel))) {
                    setScrollState(SCROLL_STATE_IDLE);
                }
                resetScroll();
            } break; }...return true; }}Copy the code

When the ACTION_UP event occurs, pass the speed calculated by VelocityTracker into recyclerView.fling () to trigger the fling:

public class RecyclerView {
    public boolean fling(int velocityX, int velocityY) {...// If the scroll speed is lower than the threshold, return does not trigger the fling
        if(! canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { velocityX =0;
        }
        if(! canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { velocityY =0;
        }
        if (velocityX == 0 && velocityY == 0) {
            return false;
        }
        // Let the parent control consume the fling ahead of time
        if(! dispatchNestedPreFling(velocityX, velocityY)) { ... velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));// Delegate to ViewFlinger trigger fling
                mViewFlinger.fling(velocityX, velocityY);
                return true; }}return false; }}Copy the code

The RecyclerView uses nested scroll logic to create a fling before it triggers the fling. If the parent controls do not consume the fling, the User can delegate the fling to the ViewFlinger.

public class RecyclerView { 
    class ViewFlinger implements Runnable {
        OverScroller mOverScroller;
        public void fling(int velocityX, int velocityY) {...// Count the values associated with fling
            mOverScroller.fling(0.0, velocityX, velocityY,Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
            // Throw ViewFlinger as an animation
            postOnAnimation();
        }
        
        void postOnAnimation(a) {...// Throw ViewFlinger as an animation
            internalPostOnAnimation();
        }
        
        private void internalPostOnAnimation(a) {
            removeCallbacks(this);
            // Post ViewFlinger to the main thread message queue
            ViewCompat.postOnAnimation(RecyclerView.this.this); }}}Copy the code

ViewFlinger is a Runnable. The list offhand scrolling logic is also wrapped in this Runnable and posted to the main message queue:

public class ViewCompat {
    public static void postOnAnimation(@NonNull View view, Runnable action) {
        if (Build.VERSION.SDK_INT >= 16) {
            // Delegate the throw message to the view
            view.postOnAnimation(action);
        } else{ view.postDelayed(action, ValueAnimator.getFrameDelay()); }}}public class View {
    public void postOnAnimation(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if(attachInfo ! =null) {
            // Delegate the throw messages to Choreographer and specify the message type as CALLBACK_ANIMATION
            attachInfo.mViewRootImpl.mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, action, null);
        } else{ getRunQueue().post(action); }}}Copy the code

Choreographer is a class for coordinating upper-level draw tasks with low-level vSYNC signals:

public final class Choreographer {
    // Enter the task
    public static final int CALLBACK_INPUT = 0;
    // Animation task
    public static final int CALLBACK_ANIMATION = 1;
    // View tree traverses tasks
    public static final int CALLBACK_TRAVERSAL = 2;
    / / COMMIT task
    public static final int CALLBACK_COMMIT = 3;
    // Hold a chained array of tasks
    private final CallbackQueue[] mCallbackQueues;
    // Main thread message handler
    private final FrameHandler mHandler;
    
    // Discard the drawing task
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    // Delay throwing the drawing task
    public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis) {... postCallbackDelayedInternal(callbackType, action, token, delayMillis); }// Throw the concrete implementation of the drawing task
    private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            // 1. Temporarily store the drawing task in the chain structure according to the type
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            // 2. Subscribe to the next vSYNC signal
            if (dueTime <= now) {
            	// Subscribe to the next vSYNC signal immediately
                scheduleFrameLocked(now);
            } else {
            	Subscribe to vSYNC signals at some point in the future
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}// Main thread message handler
    private final class FrameHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
				...
                case MSG_DO_SCHEDULE_CALLBACK:
                    Subscribe to vSYNC signal at future point in time
                    doScheduleCallback(msg.arg1);
                    break; }}}}Copy the code

Choreographer internally maintains a chained array structure called CallbackQueue[], which holds four types of tasks: input tasks, animation tasks, View tree traversal tasks, and COMMIT tasks.

These tasks are picked up and executed when the next vSYNC signal arrives.

public final class Choreographer {
    // VSYNC receiver
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
        // VSYNC signal arrives
        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {...// Send an asynchronous message to the main thread and execute the current Runnable, i.e. DoFrame ()
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run(a) {
            mHavePendingVsync = false;
            // Draw a frame of contentdoFrame(mTimestampNanos, mFrame); }}}Copy the code

Where doFrome() represents the current frame for drawing the arrival of vertical signals:

public final class Choreographer {
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
        ...
        try {
            // Handle the input event for this frame
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            // Animate this frame
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            // Handle the View tree traversal for this frame
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
	    // Execute the COMMIT task after all drawing tasks are completedoCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); }}}Copy the code

Drawing the current frame processes the four previously pending tasks in sequence. More detailed analytical knowledge can click to read the source code long | Choreographer Android caton true because “frame”?

Thus it can be concluded that:

Out-of-hand scrolling of RecyclerView is not performed immediately, when out-of-hand scrolling is triggered, a Runnable called ViewFlinger is thrown into Choreographer, which is packaged as an animation task. The task is executed on the main thread while waiting for the next vSYNC signal.

Perform the off-hand scroll task continuously

The task thrown to the main thread is in viewflinger.run () :

public class RecyclerView {
    class ViewFlinger implements Runnable {
        @Override
        public void run(a) {...final OverScroller scroller = mOverScroller;
            // OverScroller calculates the roll position and returns true to indicate that the roll is not complete
            if (scroller.computeScrollOffset()) {
                final int x = scroller.getCurrX();
                final int y = scroller.getCurrY();
                int unconsumedX = x - mLastFlingX;
                int unconsumedY = y - mLastFlingY;
                mLastFlingX = x;
                mLastFlingY = y;
                int consumedX = 0;
                int consumedY = 0;

                // Callback nested scrolling to make the parent control of RecyclerView consume the scrolling distance first
                if (dispatchNestedPreScroll(unconsumedX, unconsumedY, mReusableIntPair, null, TYPE_NON_TOUCH)) {
                    unconsumedX -= mReusableIntPair[0];
                    unconsumedY -= mReusableIntPair[1]; }...if(mAdapter ! =null) {
                    mReusableIntPair[0] = 0;
                    mReusableIntPair[1] = 0;
                    // The list scrolls by itself a little bit, and the rest of the nested scrolling is given to RecyclerView for consumption
                    scrollStep(unconsumedX, unconsumedY, mReusableIntPair);
                    consumedX = mReusableIntPair[0];
                    consumedY = mReusableIntPair[1]; unconsumedX -= consumedX; unconsumedY -= consumedY; . }...// Determine whether the current scroll position is equal to the target scroll position
                boolean scrollerFinishedX = scroller.getCurrX() == scroller.getFinalX();
                boolean scrollerFinishedY = scroller.getCurrY() == scroller.getFinalY();
                // Check whether the roll is over by using the OverScroller command
                final booleandoneScrolling = scroller.isFinished() || ((scrollerFinishedX || unconsumedX ! =0) && (scrollerFinishedY || unconsumedY ! =0));

                SmoothScroller smoothScroller = mLayout.mSmoothScroller;
                booleansmoothScrollerPending = smoothScroller ! =null && smoothScroller.isPendingInitialRun();

                if(! smoothScrollerPending && doneScrolling) { ... }else {
                    // If scrolling is not finished, continue to throw yourself (ViewFlinger) to the main thread
                    postOnAnimation();
                    if(mGapWorker ! =null) {
                        mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY); }}}... }}}Copy the code

RecyclerView’s out-of-hand scrolling is done in small sections. Each section of scrolling is implemented by recyclerView.scrollstep (). How to realize the scrolling in RecyclerView? | unlock reading the source code has been introduced in the new position, it will fill in rolling direction according to the rolling distance additional tables, then all the list items to the rolling of the same distance from the opposite direction of translation, in order to realize the rolling.

After a short roll, do I have to roll another one? This is determined by the OverScroller. If the current scrolling position is not equal to the target scrolling position, it indicates that the scrolling is not finished. At this point, postOnAnimation() is executed again to throw the ViewFlinger Runnable into the main thread. And so the list rolls on itself.

The OverScroller is used to store and calculate all scrolling related values:

public class OverScroller {
    / / lateral Scroller
    private final SplineOverScroller mScrollerX;
    / / vertical Scroller
    private final SplineOverScroller mScrollerY;
    static class SplineOverScroller {
        // The initial scroll position
        private int mStart;
        // The current scroll position
        private int mCurrentPosition;
        // Target scroll position
        private int mFinal;
        // Roll speed
        private int mVelocity;
        // Scroll start time
        private long mStartTime;
        // The scrolling duration
        private intmDuration; . }}Copy the code

In each ViewFlinger. The run () is executed at the beginning of, via OverScroll.com puteScrollOffset () triggered update roll that a short period of the displacement value:

public class OverScroller {
    public boolean computeScrollOffset(a) {
        if (isFinished()) {
            return false;
        }

        switch (mMode) {
            // The algorithm for calculating the displacement of the handroll
            case SCROLL_MODE:
                long time = AnimationUtils.currentAnimationTimeMillis();
                final long elapsedTime = time - mScrollerX.mStartTime;

                final int duration = mScrollerX.mDuration;
                if (elapsedTime < duration) {
                    final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
                    mScrollerX.updateScroll(q);
                    mScrollerY.updateScroll(q);
                } else {
                    abortAnimation();
                }
                break;
            // Calculate the displacement of the roll
            case FLING_MODE:
                / / lateral
                if(! mScrollerX.mFinished) {if(! mScrollerX.update()) {if(! mScrollerX.continueWhenFinished()) { mScrollerX.finish(); }}}/ / the longitudinal
                if(! mScrollerY.mFinished) {if(! mScrollerY.update()) {if(! mScrollerY.continueWhenFinished()) { mScrollerY.finish(); }}}break;
        }
        return true; }}Copy the code

For Fling, the algorithm for calculating the roll displacement is in the update() method:

public class OverScroller {
    static class SplineOverScroller {
        // Update the current location according to some algorithm
        boolean update(a) {
            final long time = AnimationUtils.currentAnimationTimeMillis();
            final long currentTime = time - mStartTime;

            if (currentTime == 0) {
                return mDuration > 0;
            }
            if (currentTime > mDuration) {
                return false;
            }

            double distance = 0.0;
            // Select different algorithms to calculate the scrolling speed and current scrolling position according to different states
            switch (mState) {
                case SPLINE: {
                    final float t = (float) currentTime / mSplineDuration;
                    final int index = (int) (NB_SAMPLES * t);
                    float distanceCoef = 1.f;
                    float velocityCoef = 0.f;
                    if (index < NB_SAMPLES) {
                        final float t_inf = (float) index / NB_SAMPLES;
                        final float t_sup = (float) (index + 1) / NB_SAMPLES;
                        final float d_inf = SPLINE_POSITION[index];
                        final float d_sup = SPLINE_POSITION[index + 1];
                        velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                        distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                    }

                    distance = distanceCoef * mSplineDistance;
                    mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0 f;
                    break;
                }
                case BALLISTIC: {
                    final float t = currentTime / 1000.0 f;
                    mCurrVelocity = mVelocity + mDeceleration * t;
                    distance = mVelocity * t + mDeceleration * t * t / 2.0 f;
                    break;
                }
                case CUBIC: {
                    final float t = (float) (currentTime) / mDuration;
                    final float t2 = t * t;
                    final float sign = Math.signum(mVelocity);
                    distance = sign * mOver * (3.0 f * t2 - 2.0 f * t * t2); 
                    mCurrVelocity = sign * mOver * 6.0 f * (- t + t2); 
                    break; }}// Update the current scroll position
            mCurrentPosition = mStart + (int) Math.round(distance);
            return true; }}}Copy the code

The algorithm is complicated and hard to understand (I guess the phone manufacturer will optimize it for a smoother feel), but it doesn’t affect the topic of today’s discussion. Dig deeper later when there is demand.

The end of the algorithm is to update the value of the current scrolling position, mCurrentPosition.

conclusion

  • For RecyclerView, regardless of hand or hand rolling, the implementation of the final rolling is through the callView.offsetTopAndBottom()Pan the entry in the opposite direction of the scroll.
  • OverScrollerNot only do we store and calculate all the values associated with scrolling. RecyclerView also uses it to control whether rolling should continue.
  • RecyclerView’s free rolling is carried out section by section, each section of the roll is wrapped in a calledViewFlingerThe Runnable. It’s going to be thrownChoreographerTo be temporarily saved as an animation task. When the next vSYNC signal arrives, it is thrown to the main thread’s message queue for execution.
  • As long asOverScrollerJudging that the roll is not over,ViewFlingerThe process is repeated, once again throwing itself into the main thread. Repeat, and the list rolls out of hand.

Recommended reading

RecyclerView series article directory is as follows:

RecyclerView series article directory is as follows:

  1. RecyclerView caching mechanism | how to reuse table?

  2. What RecyclerView caching mechanism | recycling?

  3. RecyclerView caching mechanism | recycling where?

  4. RecyclerView caching mechanism | scrap the view of life cycle

  5. Read the source code long knowledge better RecyclerView | click listener

  6. Proxy mode application | every time for the new type RecyclerView is crazy

  7. Better RecyclerView table sub control click listener

  8. More efficient refresh RecyclerView | DiffUtil secondary packaging

  9. Change an idea, super simple RecyclerView preloading

  10. RecyclerView animation principle | change the posture to see the source code (pre – layout)

  11. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

  12. RecyclerView animation principle | how to store and use animation attribute values?

  13. RecyclerView list of interview questions | scroll, how the list items are filled or recycled?

  14. RecyclerView interview question | what item in the table below is recycled to the cache pool?

  15. RecyclerView performance optimization | to halve load time table item (a)

  16. RecyclerView performance optimization | to halve load time table item (2)

  17. RecyclerView performance optimization | to halve load time table item (3)

  18. How does RecyclerView roll? (a) | unlock reading source new posture

  19. RecyclerView how to achieve the scrolling? (2) | Fling

  20. RecyclerView Refresh list data notifyDataSetChanged() why is it expensive?