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 call
View.offsetTopAndBottom()
Pan the entry in the opposite direction of the scroll. OverScroller
Not 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 called
ViewFlinger
The Runnable. It’s going to be thrownChoreographer
To 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 as
OverScroller
Judging that the roll is not over,ViewFlinger
The 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:
-
RecyclerView caching mechanism | how to reuse table?
-
What RecyclerView caching mechanism | recycling?
-
RecyclerView caching mechanism | recycling where?
-
RecyclerView caching mechanism | scrap the view of life cycle
-
Read the source code long knowledge better RecyclerView | click listener
-
Proxy mode application | every time for the new type RecyclerView is crazy
-
Better RecyclerView table sub control click listener
-
More efficient refresh RecyclerView | DiffUtil secondary packaging
-
Change an idea, super simple RecyclerView preloading
-
RecyclerView animation principle | change the posture to see the source code (pre – layout)
-
RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache
-
RecyclerView animation principle | how to store and use animation attribute values?
-
RecyclerView list of interview questions | scroll, how the list items are filled or recycled?
-
RecyclerView interview question | what item in the table below is recycled to the cache pool?
-
RecyclerView performance optimization | to halve load time table item (a)
-
RecyclerView performance optimization | to halve load time table item (2)
-
RecyclerView performance optimization | to halve load time table item (3)
-
How does RecyclerView roll? (a) | unlock reading source new posture
-
RecyclerView how to achieve the scrolling? (2) | Fling
-
RecyclerView Refresh list data notifyDataSetChanged() why is it expensive?