RecyclerView, as a list View, slides naturally. As a user, we can not go to understand how it is to slide, but we as a learning source, must know the sliding mechanism of RecyclerView, so, today we come to see the code of RecyclerView sliding part.
References for this article:
- Android source code analysis – nested sliding mechanism implementation principle
- Deep RecyclerView source code explore three: drawing and sliding
At the same time, from the perspective of RecyclerView class structure, we know that RecyclerView realizes NestedScrollingChild interface, so RecyclerView is also a View that can produce sliding events. I’m sure you’ve all used the combination of CoordinatorLayout and RecyclerView, which is nested sliding. This article in the introduction of ordinary sliding, may involve the knowledge of nested sliding, so when reading this article, we need to master the mechanism of nested sliding, can refer to my above article: Android source code analysis – the realization of the principle of nested sliding mechanism, this article specifically from the perspective of RecyclerView to understand the mechanism of nested sliding.
This paper intends to analyze RecyclerView from the following aspects:
- The normal
TouchEvent
- Nested slides (interspersed throughout the article, not specifically explained)
- More refers to the sliding
- Fling sliding
1. Traditional events
Now that we’re officially analyzing the source code, let’s first look at the onTouchEvent method and see what it does for us:
@override public Boolean onTouchEvent(MotionEvent e) {// ······if (dispatchOnItemTouch(e)) {
cancelTouch();
return true; } // switch (action) {caseMotionevent.action_down: {// ····}break;
caseMotionevent.action_pointer_down: {// ····}break;
caseACTION_MOVE: {// ·····}break;
caseACTION_POINTER_UP: {// ····}break;
caseMotionevent.action_up: {// ····}break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break; } / /......return true;
}
Copy the code
As above is the onTouchEvent method of RecyclerView, I greatly simplified this method, first let you have an understanding of its structure.
I believe you are familiar with ACTION_DOWN, ACTION_MOVE, ACTION_UP and ACTION_CANCEL, which are the most basic events of View.
For those of you who are unfamiliar with ACTION_POINTER_DOWN and ACTION_POINTER_UP events, these two events are related to multi-finger sliding and are one of the focuses of this article.
All right, let’s start formally analyzing the source code. Before analyzing the source code, I’ll give a brief overview of the above code.
- If the current
mActiveOnItemTouchListener
If you need to consume the current event, give it priority.- if
mActiveOnItemTouchListener
Instead of consuming the current event, the normal event distribution mechanism is used. There are a lot of details, and I’ll go into them later.
I don’t need to explain the first step, it is a Listener callback, very simple, we focus on analyzing the second step.
(1). The Down event
Let’s look at this part of the code first.
caseMotionEvent.ACTION_DOWN: { mScrollPointerId = e.getPointerId(0); MInitialTouchX = mLastTouchX = (int) (LLDB () + 0.5f); MInitialTouchY = mLastTouchY = (int) (LLDB () + 0.5f); int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
Copy the code
There are two main things that have been done here.
- Record the x and y coordinates of the Down event.
- call
startNestedScroll
Method, ask the parentView
Whether to process events.
The Down event is still relatively simple, usually just initialization.
Next, let’s look at the main event, the Move event
(2). Move events
Let’s take a look at this part of the code:
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id "
+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false; } final int x = (int) (LLDB etX(index) + 0.5f); Final int y = (int) (lltety (index) + 0.5f); int dx = mLastTouchX - x; int dy = mLastTouchY - y;if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if(mScrollState ! = SCROLL_STATE_DRAGGING) { boolean startScroll =false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING); }}if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if(mGapWorker ! = null && (dx ! = 0 || dy ! = 0)) { mGapWorker.postFromTraversal(this, dx, dy); }}}break;
Copy the code
This part of the code is very simple, I divided it into the following steps:
- Calculate dx and dy according to the x and y coordinates generated by the Move event.
- call
dispatchNestedPreScroll
Ask of the fatherView
Whether the sliding event is processed first, if consumed, dx and dy subtract the parent, respectivelyView
The portion of the distance consumed.- Then judge according to the situation
RecyclerView
Whether you slide vertically or horizontally is ultimately the callscrollByInternal
Method to achieve the effect of sliding.- call
GapWorker
thepostFromTraversal
To prefetchViewHolder
. This procedure follows some of the logic of the caching mechanism and may be calledAdapter
theonBindViewHolder
Method to preload data.
The first and second steps are relatively simple and will be omitted here.
And the scrollByInternal method is very simple, so inside the scrollByInternal method, This is actually done by calling either the scrollHorizontallyBy or scrollVerticallyBy method of LayoutManager. The LayoutManager method does not actually do any dirty operations, after all, the final call is to call each Child offsetTopAndBottom or offsetLeftAndRight method to achieve, there is not a single trace code, You get the idea. At the end of this article, I will write a simple Demo according to the code related to RecyclerView sliding.
Here, we simply analyze how GapWorker prefetches. Let’s look at the postFromTraversal method:
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
if(RecyclerView.DEBUG && ! mRecyclerViews.contains(recyclerView)) { throw new IllegalStateException("attempting to post unregistered view!");
}
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
recyclerView.post(this);
}
}
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
Copy the code
Not much is done inside the postFromTraversal method, except that the post method is called to add a Runnable to the task queue. GapWorker’s run method is the most important analysis:
@Override
public void run() {
try {
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
if (mRecyclerViews.isEmpty()) {
// abort - no work to do
return;
}
// Query most recent vsync so we can predict next one. Note that drawing time not yet
// valid in animation/input callbacks, so query it here to be safe.
final int size = mRecyclerViews.size();
long latestFrameVsyncMs = 0;
for (int i = 0; i < size; i++) {
RecyclerView view = mRecyclerViews.get(i);
if(view.getWindowVisibility() == View.VISIBLE) { latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs); }}if (latestFrameVsyncMs == 0) {
// abort - either no views visible, or couldn't get last vsync for estimating next return; } long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs; prefetch(nextFrameNs); // TODO: consider rescheduling self, if there's more work to do} finally { mPostTimeNs = 0; TraceCompat.endSection(); }}Copy the code
The logic of the run method is also very simple, calculating the time to fetch the next frame and then calling the prefetch method to prefetch the ViewHolder.
void prefetch(long deadlineNs) {
buildTaskList();
flushTasksWithDeadline(deadlineNs);
}
Copy the code
The prefetch method is also simple, showing the task queue generated by calling buildTaskList, Then call flushTasksWithDeadline to perform the task, which will invoke the RecyclerView tryGetViewHolderForPositionByDeadline method to get a ViewHolder, not analyzed one by one here.
But need to note is, tryGetViewHolderForPositionByDeadline method is the core of the whole RecyclerView caching mechanism, RecyclerView caching mechanism in the embodiment of the method was brought out. About this method, if there is no accident, in the next article we can touch, here, first to sell you a pass 😂.
Finally, there are Up events and Cancel events. These two events are more simple, and both of them perform some cleaning operations, which will not be analyzed here. However, one of the fling events that can occur in an Up event is the Fling event. We will analyze the fling event in detail.
2. Swipe with multiple fingers
We must not misunderstand here refers to the meaning of sliding, here refers to the multiple sliding not RecyclerView can be corresponding to the sliding of multiple fingers, but refers to when a finger has not been released, at this time another finger press, RecyclerView does not correspond to a finger gesture, but the corresponding recent finger gesture.
Let’s look at this part of the code:
case MotionEvent.ACTION_POINTER_DOWN: {
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
} break;
Copy the code
When another finger is pressed, the coordinates are immediately updated and the mScrollPointerId is updated, indicating that only the most recent finger gesture will be followed.
Second, let’s look at the case of multiple fingers being loosened:
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
Copy the code
private void onPointerUp(MotionEvent e) {
final int actionIndex = e.getActionIndex();
if(e.getPointerId(actionIndex) == mScrollPointerId) { // Pick a new pointer to pick up the slack. final int newIndex = actionIndex == 0 ? 1:0; mScrollPointerId = e.getPointerId(newIndex); MInitialTouchX = mLastTouchX = (int) (LLDB (newIndex) + 0.5F); MInitialTouchY = mLastTouchY = (int) (lltety (newIndex) + 0.5f); }}Copy the code
There is no more elegant operation here, just ordinary update. I’m not going to explain it in detail here. There will be a small Demo at the end of this article to show you the effect of RecyclerView.
The last slide, the most important slide in this paper, is the Fling slide. Fling events are one of the most overlooked events in our custom View.
3. Slide fling
Let’s first look at where the Fling slide occurs and the Up event occurs:
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
if(! ((xvel ! = 0 || yvel ! = 0) && fling((int) xvel, (int) yvel))) {setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
Copy the code
The fling method is called and the fling method is not in place.
Public Boolean fling(int velocityX, int velocityY) {fling(int velocityX, int velocityY)if(! dispatchNestedPreFling(velocityX, velocityY)) { final boolean canScroll = canScrollHorizontal || canScrollVertical; dispatchNestedFling(velocityX, velocityY, canScroll);if(mOnFlingListener ! = null && mOnFlingListener.onFling(velocityX, velocityY)) {return true;
}
if (canScroll) {
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontal) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertical) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH);
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
mViewFlinger.fling(velocityX, velocityY);
return true; }}return false;
}
Copy the code
In the fling method, the dispatchNestedPreFling method asks the parent View to process the fling event. The best way to fling your body is to hold it to its own place and fling it to its own place.
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
Copy the code
In ViewFlinger’s fling method, the OverScroller fling is called to calculate the fling parameters, including the fling distance and fling time. I won’t go into the calculation code here, because it’s all about math and physics. Finally, the postOnAnimation method is called.
void postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else{ removeCallbacks(this); ViewCompat.postOnAnimation(RecyclerView.this, this); }}Copy the code
You might not understand the code above, but it’s pretty much like a View post, so you have to look at ViewFlinger’s run method.
ViewFlinger’s run method is long, so I’ll simplify it a bit:
public void run() {// ······ ···· // First step, update the scroll information, and determine whether the current scroll has been completed // istrueIndicates that scrolling is not completeif(scroller.com puteScrollOffset ()) {/ / · · · · · ·if(mAdapter ! = null) {// ······ // Scroll a certain distanceif(dx ! = 0) { hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); overscrollX = dx - hresult; }if(dy ! = 0) { vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); overscrollY = dy - vresult; } / /......} / /... / / if the rolling finished, is to call finish method; // If the scroll is not completed, call postOnAnimation to continue the recursionif(scroller.isFinished() || (! fullyConsumedAny && ! hasNestedScrollingParent(TYPE_NON_TOUCH))) { // setting state to idle will stop this.setScrollState(SCROLL_STATE_IDLE);
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
stopNestedScroll(TYPE_NON_TOUCH);
} else {
postOnAnimation();
if(mGapWorker ! = null) { mGapWorker.postFromTraversal(RecyclerView.this, dx, dy); }}} // ······ ··}Copy the code
The heart lies in its own place and lies in its own place. There are three steps to achieve the best fling.
We analyzed the Fling events of RecyclerView, how can we help? In daily development, if you need to fling, we can use RecyclerView to achieve the effect, is it very simple? Right, this is the purpose of learning source code, not only to understand the principle, but also need to learn to apply 😂.
4. The Demo show
Here demo is not very lofty things, is according to RecyclerView code to achieve a multi-finger sliding View. Let’s have a look at the source code:
public class MoveView extends View {
private int mLastTouchX;
private int mLastTouchY;
private int mTouchSlop;
private boolean mCanMove;
private int mScrollPointerId;
public MoveView(Context context) {
this(context, null);
}
public MoveView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int actionIndex = event.getActionIndex();
switch (event.getActionMasked()){
caseMotionEvent.ACTION_DOWN: mScrollPointerId = event.getPointerId(0); MLastTouchX = (int) (event.getx () + 0.5f); MLastTouchY = (int) (event.gety () + 0.5f); mCanMove =false;
break;
caseMotionEvent.ACTION_POINTER_DOWN: mScrollPointerId = event.getPointerId(actionIndex); MLastTouchX = (int) (event.getx (actionIndex) + 0.5f); MLastTouchY = (int) (event.gety (actionIndex) + 0.5f);break;
caseMotionEvent.ACTION_MOVE: final int index = event.findPointerIndex(mScrollPointerId); Int x = (int) (event.getx (index) + 0.5f); Int y = (int) (event.gety (index) + 0.5f); int dx = mLastTouchX - x; int dy = mLastTouchY - y;if(! mCanMove) {if (Math.abs(dy) >= mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
mCanMove = true;
}
if (Math.abs(dy) >= mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
mCanMove = true; }}if (mCanMove) {
offsetTopAndBottom(-dy);
offsetLeftAndRight(-dx);
}
break;
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(event);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
private void onPointerUp(MotionEvent e) {
final int actionIndex = e.getActionIndex();
if(e.getPointerId(actionIndex) == mScrollPointerId) { final int newIndex = actionIndex == 0 ? 1:0; mScrollPointerId = e.getPointerId(newIndex); MLastTouchX = (int) (LLDB (newIndex) + 0.5f); MLastTouchY = (int) (lltety (newIndex) + 0.5f); }}}Copy the code
I believe that after RecyclerView source code learning, the understanding of the above code is not difficult, so here I do not need to explain. Specific effects, you can copy Android Studio inside to see 😂.
4. To summarize
In comparison, the sliding mechanism of RecyclerView is still very simple. I also feel that there is nothing to sum up. But from RecyclerView source code, we can learn two points:
- Multi-finger slide. We can base it on
RecyclerView
The source code, to achieve their own multi – finger sliding, this is a reference, but also learn to applyfling
The slide.RecyclerView
To achieve thefling
Effect, in the daily development process, if we also need to achieve this effect, we can according toRecyclerView
To achieve the source code.