Preface: If a man has no dream, what is the difference between him and salted fish? — Shaolin Football
1. Drawing process
RecyclerView support all kinds of layout effect, its core is the key to RecyclerView. LayoutManager, is we need when using setLayoutManager () sets the layout manager. RecyclerView has been part of the function out, in the layout manager for additional processing, but also convenient for developers to expand. LayoutManager is responsible for the measurement and layout of RecyclerView and the recycling and reuse of itemView. Today here mainly combined with LinearLayoutManager to analyze the drawing process of RecyclerView.
RecyclerView provides three types of layout managers:
- The LinearLayoutManager displays items in a list. There is a HORIZONTAL RecyclerView.HORIZONTAL and VERTICAL RecyclerView.
- The GridLayoutManager presents items as a grid, horizontal and vertical;
- StaggeredGridLayoutManager display item, in the form of cascade flow with horizontal and vertical direction.
Here take LinearLayoutManager as an example to analyze the drawing process of RecyclerView.
Warm prompt: in this paper, the source code based on androidx. Recyclerview: recyclerview: 1.2.0 – alpha01
1.1. Three steps of RecyclerView
RecyclerView set LayoutManager, this step is necessary, what kind of LayoutManager to draw RecyclerView, otherwise RecyclerView does not know how to draw.
recyclerView.setLayoutManager(manager);
Copy the code
SetLayoutManager () sets the RecyclerView layout manager to use:
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {// Just like the previous manager, return directly
return;
}
stopScroll();// Stop scrolling
if(mLayout ! =null) {// Every time you set layoutManager, reset the initial parameters of recyclerView
if(mItemAnimator ! =null) {
mItemAnimator.endAnimations();// End the animation
}
mLayout.removeAndRecycleAllViews(mRecycler);// Remove and recycle all ItemViews
mLayout.removeAndRecycleScrapInt(mRecycler);// Remove and recycle all discarded ItemViews
mRecycler.clear();// Clear all caches
mLayout.setRecyclerView(null);/ / reset RecyclerView
mLayout = null;
} else{ mRecycler.clear(); } · · · · · · · mLayout. SetRecyclerView (this);//LayoutManager is associated with RecyclerView
mRecycler.updateViewCacheSize();// Update the cache size
requestLayout();// Request a redraw
}
Copy the code
Here the reset recycle is done first, then the LayoutManager is associated with the RecyclerView, and finally the request is redrawn. RequestLayout () = onMeasure(), onLayout(), onDraw() = onMeasure();
public void requestLayout(a) {
if(mRecyclerView ! =null) {
mRecyclerView.requestLayout();// Request a redraw}}Copy the code
1. onMeasure()
Take a look at the RecyclerView onMeasure() method:
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {// If mLayout is empty, take the default measurement and finish
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {// For automatic measurement, the default is true
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);// Measure the width of RecyclerView
// Whether the current width and height of RecyclerView is accurate
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {// If the width and height of RecyclerView is exact or the mAdapter is empty, end
return;
}
MeasureSpecModeIsExactly = false when the width and height of RecyclerView is wrAP_content
// Because the width and height of RecyclerView is wrAP_content, you need to measure the width and height of itemView first to know the width and height of RecyclerView
if (mState.mLayoutStep == State.STEP_START) {// It has not been measured
dispatchLayoutStep1();//1. The adapter updates, animates, saves information about the current view, and runs the predictive layout
}
dispatchLayoutStep2();//2. The final actual layout view will be run multiple times if necessary
// Get the width and height of RecyclerView according to itemViewmLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); }}Copy the code
OnMeasure () is mainly RecyclerView width and height measurement work, mainly in two cases:
- (1) When the width and height of RecyclerView is match_parent or an exact value, that is
measureSpecModeIsExactly = true
, at this time only need to measure its width and height to know the width and height of RecyclerView, the measurement method ends; - (2) When the width and height of RecyclerView is WRAP_content, that is
measureSpecModeIsExactly = false
, will execute downdispatchLayoutStep1()
anddispatchLayoutStep2()
, is traversed to measure the size of ItemView to determine the width and height of RecyclerView. In this case, the real measurement operation is indispatchLayoutStep2()
In the finish.
DispatchLayoutStep1 () and dispatchLayoutStep2() will be explained below.
2. onLayout()
In the onLayout() method, call the dispatchLayout() method directly:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout(); // Call the dispatchLayout() method layout directly
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
Copy the code
DispatchLayout () is a wrapper around layoutChildren(), which handles dynamic changes caused by the layout:
void dispatchLayout(a) {· · · · · · mState. MIsMeasuring =false;// Set RecyclerView layout to complete state.
if (mState.mLayoutStep == State.STEP_START) {// If the subitemView is not measured in advance in the OnMeasure phase
dispatchLayoutStep1();// Layout step 1: Adapter update, animate run, save current view information, run predictive layout
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if(mAdapterHelper.hasUpdates() || mLayout.getWidth() ! = getWidth() || mLayout.getHeight() ! = getHeight()) {// The first two steps complete the measurement, but have to run the following code again because of the size change
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();// Layout step 2: The final actual layout view, run multiple times if necessary
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();// Layout step 3: The layout of the last step, save the view animation, trigger animation and unnecessary cleanup.
}
Copy the code
You can see that the three steps of the dispatchLayout() phase selectively measure the layout as in the onMeasure() phase:
- 1. If the sub-ItemView is not measured in advance in onMeasure stage, that is, the width and height of RecyclerView is
match_parent
Or exact value when calleddispatchLayoutStep1()
anddispatchLayoutStep2()
Measure the width and height of itemView - 2. If the subItemView is measured in advance during the onMeasure phase, but the subview changes or the expected width is inconsistent with the actual width and height, it will be called
dispatchLayoutStep2()
Remeasure; - 3. It will be executed at the end
dispatchLayoutStep3()
Methods.
DispatchLayoutStep1 () is mainly for pre-layout, adapter update, animation operation, save the information of the current view and so on;
private void dispatchLayoutStep1(a) {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();// Intercept layout requests
mViewInfoStore.clear();//itemView information is cleared
onEnterLayoutOrScroll();
// When measuring and dispatching layouts, update adapters and calculate which type of animation to run
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();// Save focus information
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);// Find the minimum and maximum position to draw itemView
if (mState.mRunSimpleAnimations) {
// Get the number of items that can be displayed on the interface
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
// Animation information
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
// Save the holder and animation information to the pre-layoutmViewInfoStore.addToPreLayout(holder, animationInfo); }}// Run with layout, will use the old item position, layout manager layout all
if (mState.mRunPredictiveAnimations) {
// Save the logic that the old manager can run
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
/ / layout itemView
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
}
stopInterceptRequestLayout(false);// Restore draw lock
mState.mLayoutStep = State.STEP_LAYOUT;
}
Copy the code
(2) dispatchLayoutStep2() indicates the actual layout of the view of the final state:
private void dispatchLayoutStep2(a) {
startInterceptRequestLayout();// Intercept the request layout
onEnterLayoutOrScroll();
// Set the layout state and animation state
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// The prelayout is complete, and the layout of itemView is started
mState.mInPreLayout = false; mLayout.onLayoutChildren(mRecycler, mState); · · · · · · stopInterceptRequestLayout (false);// Stop intercepting layout requests
}
Copy the code
(3) dispatchLayoutStep3() is the last step of the layout, save the animation information of the view, execute the animation, and do some necessary cleaning:
private void dispatchLayoutStep3(a) {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();// Start intercepting layout requests
mState.mLayoutStep = State.STEP_START;// The layout starts
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now and process the change animation.
// Reverse traverse the list, because we might call animateChange in the loop, which might remove the target view holder.
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
// Run a change animation. If an item is changed, but the updated version is disappearing, a conflict situation can occur.
// Since views marked as disappearing may be out of bounds, we run a change animation. Both views will be cleared automatically after the animation is complete.
// On the other hand, if it is the same viewholder instance, we will run a vanishing animation because we will not rebind the updated VH unless it is enforced by the layout manager.
// Run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(oldChangeViewHolder);
// We add and delete so that any layout information is merged
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
// Handle view information lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
// Recycle obsolete views
mLayout.removeAndRecycleScrapInt(mRecycler);
// Reset the state
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
// Clear data from mChangedScrap
mRecycler.mChangedScrap.clear();
mRecycler.updateViewCacheSize();// Update the cache size
mLayout.onLayoutCompleted(mState);// The layout is complete
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);// Stop intercepting layout requests
mViewInfoStore.clear();//itemView information is cleared
recoverFocusFromState();// Return to focus
resetFocusInfo();// Reset the focus information
}
Copy the code
Summarize the three steps of the distribution layout:
- DispatchLayoutStep1 () : pre-layout, adapter update, animation run, save current view information, etc.
- DispatchLayoutStep2 () : indicates the actual layout of the final state view, several times if necessary;
- DispatchLayoutStep3 () : indicates the last step of layout, saves and triggers information about animation, related cleanup, etc.
3. onDraw()
In the last step of the onDraw() method, TextView and ImageView controls are already drawn if no special effects are needed.
@Override
public void onDraw(Canvas c) {
super.onDraw(c);// All ItemViews are drawn first
// Draw ItemDecoration separately
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState); }}Copy the code
4. Summary of drawing process:
1. The itemView of RecyclerView may be measured for many times. If the width and height of RecyclerView is fixed or match_parent, then inonMeasure()
In the stage, ItemView layout will not be measured in advance. If the width and height of RecyclerView is WRAP_content, the actual width and height of RecyclerView will be measured in advanceonMeasure()
Stage traversal measurement of itemView layout to determine the width and height value of the content display area to determine the actual width and height of RecyclerView;
2. DispatchLayoutStep1 (), dispatchLayoutStep2(), dispatchLayoutStep3() these three methods will be executed. When the actual width and height of RecyclerView is uncertain, DispatchLayoutStep1 (), dispatchLayoutStep2(), and finally dispatchLayoutStep3() in onLayout(). If the itemView changes, dispatchLayoutStep2() will be executed again.
3. The measurement and layout of the itemView is actually in the dispatchLayoutStep2() method.
RecyclerView drawing three step flow chart:
2.2 LinearLayoutManager filling, measuring and layout processes
The drawing of RecyclerView goes through measure, layout and draw, but the real layout of itemView is entrusted to each LayoutManager. The LinearLayoutManager knows that dispatchLayoutStep2() is the actual layout view step, and the LayoutManager calls onLayoutChildren() to layout the itemView. It is the core method for drawing an itemView, which lists all related subviews from a given adapter.
1. OnLayoutChildren () layout itemView
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 1) Check subclasses and other variables to find trace point coordinates and trace point positions
// 2) Fill from the beginning, stack from the bottom
// 3) Fill from the bottom, stack from the top
// 4) Stack from the bottom to meet the demand
// Create the layout state
if(mPendingSavedState ! =null|| mPendingScrollPosition ! = RecyclerView.NO_POSITION) {if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);// Remove all child views
return;
}
}
ensureLayoutState();
mLayoutState.mRecycle = false;// Disallow collection
// Draw the layout upside down
resolveShouldLayoutReverse();
final View focused = getFocusedChild();// Get the child currently in focus
if(! mAnchorInfo.mValid || mPendingScrollPosition ! = RecyclerView.NO_POSITION || mPendingSavedState ! =null) {
mAnchorInfo.reset();// Reset the anchor information
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
//1. Calculate the position and coordinates of the update point
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true; } · · · · · · ·// Calculate the direction of the first layout
int startOffset;
int endOffset;
final int firstLayoutDirection;
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);// Temporarily detach the attached view, detach all child detach and recycle it through Scrap
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
mLayoutState.mNoRecycleSpace = 0;
//2. Start filling, stack from bottom;
if (mAnchorInfo.mLayoutFromEnd) {
Fill in the ItemView layout from the start position
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);// Populate all ItemViews
Fill in the ItemView layout from the end position
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);// Populate all ItemViews
endOffset = mLayoutState.mOffset;
}else { //3. Bottom fill, stack from top to bottom;
Fill in the ItemView layout from the end position
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
Fill in the ItemView layout from the start position
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
//4. Calculate the scroll offset and call the fill method to fill the new ItemView if necessary
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
}
Copy the code
First state judgment and some preparation work, and to trace point selection and update information, detachAndScrapAttachedViews (recycler) temporary will have additional view separation, cache Scrap, next time to fill out the reuse directly. Then calculate which direction to start the layout. The layout algorithm is as follows:
- 1. Find an anchor point coordinate and the position of an anchor point item by examining child elements and other variables;
- 2. Start filling and stack from the bottom.
- 3. Bottom filling, stacked from top to bottom;
- 4. Scroll to meet requirements, such as stack from the bottom.
2. Fill () starts filling the itemView
The filling layout is handed to the fill() method, which fills the given layout defined by layoutState. Why fill it twice? Let’s look at the fill() method:
// Fill method, which returns the pixels to fill the itemView for subsequent scrolling
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
recycleByLayoutState(recycler, layoutState);// Retrieve the view that slides out of the screen
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// Core == while() loop ==
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {// Loop until there is no data
layoutChunkResult.resetInternal();
// Populate the core method of itemViewlayoutChunk(recycler, state, layoutState, layoutChunkResult); · · · · · ·if (layoutChunkResult.mFinished) {// The layout ends, and the loop exits
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;// Based on the height offset of the added child} · · · · · ·return start - layoutState.mAvailable;// Returns the size of the region to be filled this time
}
Copy the code
The fill() core is a while() loop that executes layoutChunk() to fill an itemView to the screen and returns the size of the filled area. So we’re going to start with how much remaining remainingSpace we have on the screen, and we’re going to subtract the amount of space that the sub-views are taking up from that, and we’re going to finish the sub-view layout when it’s less than 0, and if we haven’t already surpassed the remainingSpace, Call layoutChunk() to position the View.
3. LayoutChunk () creates, populates, measures, and lays out itemView
LayoutChunk () is the method to fill the layout of the itemView. There are mainly the following steps for creating, filling, measuring and laying out the itemView:
- 1.
layoutState.next(recycler)
Get an itemView from the cache, if not, create an itemView; - 2. According to the actual situation to add itemView to RecyclerView, the final call or ViewGroup
addView()
Methods; - 3.
measureChildWithMargins()
Measuring itemView size includes padding for the parent view, item decoration, and margins for the child view. - B: Left, top, right, bottom
layoutDecoratedWithMargins()
Layout the given itemView in RecyclerView using coordinates.
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
//1. Get or create an itemView from the cache
View view = layoutState.next(recycler);// Get the View to display for the current postion· · · · · ·// Add itemView to RecyclerView; // Add itemView to RecyclerView
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0); }}//3. Measuring the size of the child View includes the padding of the parent View, the item decoration, and the margins of the child View
measureChildWithMargins(view, 0.0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// Calculate the left, top, right, and bottom coordinates of an ItemView
intleft, top, right, bottom; · · · · · ·//4. Use the coordinates in RecyclerView to create the given itemView
// Calculate the correct layout position, subtract margin, calculate all view boundary box (including margin and decoration)
layoutDecoratedWithMargins(view, left, top, right, bottom);// Call child.layout for layout
}
Copy the code
Get an itemView from the cache by layoutstate.next (). If not, create a new itemView. Then addView() adds an itemView to RecyclerView as needed. The final call to the ViewGroup’s addView() method is followed by measureChildWithMargins(), which measures the size of the child views including the parent View’s padding, the item trim, and the margins of the child views. Finally getDecoratedMeasuredWidth () by computing the good left, top, right, bottom values in RecyclerView coordinates given itemView layout, note that there is the width of the total width of the item + decoration.
View next(RecyclerView.Recycler recycler) {
if(mScrapList ! =null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
Copy the code
Get the itemView and use the cached View if there is a cached View in mScrapList, or create the View if there is no mScrapList and add it to mScrapList. The getViewForPosition() method is a RecyclerView cache mechanism that will be explained in subsequent articles.
LinearLayoutManager filling, measuring, layout process summary:
onLayoutChildren()
Represents a list of all related subviews from a given adapter, and the fill layout is handed infill()
Method to populate the given layout defined by layoutState, executed through the while() looplayoutChunk()
Fill an itemView to the screen as a way to finally fill the layout itemView,layoutState.next(recycler)
Get or create an itemView from the cache, add an itemView to RecyclerView by addView(), and then call the ViewGroupaddView()
Method,measureChildWithMargins()
Measuring the itemView size includes the padding of the parent view, the item trim, and the margins of the child view, and finallylayoutDecoratedWithMargins()
According to the calculated left, top, right, bottom through the use of coordinates in RecyclerView layout of the given itemView.
The flow chart is as follows:
Two, sliding principle
RecyclerView, as a list control, has its own sliding function, which is often used in the actual development. Its sliding principle is also what we need to master. It is called “know what it is, know what it is”. The sliding event processing of RecyclerView is still responded by onTouchEvent() touch event. The difference is that RecyclerView uses nested sliding mechanism, which will notify the sliding event to the parent View supporting nested sliding to make a decision first. This article may involve the knowledge of nested sliding in the process of introducing ordinary sliding (the next article will analyze nested sliding). Let’s take a look at the effect picture of ordinary sliding first:
2.1, onTouchEvent ()
RecyclerView event processing is still by onTouchEvent() touch event response, here to add a little knowledge of onTouchEvent(), familiar can be skipped.
- Boolean onTouchEvent(MotionEvent event) : Implement this method to handle touch screen motion events, return true for event processing, false for event processing;
- Motionevent. ACTION_DOWN: finger down, a down gesture has started, the action includes the initial starting position;
- Motionevent.action_move: The movement of the finger, which changes when the gesture is pressed (between down and up), contains the most recent point, and any intermediate points since the last down or move event;
- Motionevent. ACTION_UP: The finger leaves, a pressed gesture has been completed, and the action contains a final post position and any intermediate points since the last down or move event;
- MotionEvent.ACTION_CANCEL: The gesture is cancelled, the current gesture is terminated, you don’t get any more coordinates, you can treat this as an up event, but don’t do anything you would normally do;
- Motionevent. ACTION_POINTER_DOWN: When multiple fingers are pressed, a non-primary touch point is falling;
- Motionevent. ACTION_POINTER_UP: Multiple fingers leave and one non-primary touch point rises;
- Motionevent.action_outside: The finger touches outside the normal boundaries, and the movement occurs outside the normal range of UI elements. This does not provide a complete gesture, but only the initial position of the motion touch; Note that because the location of any event is outside the boundaries of the view hierarchy, it is not assigned to any child element of the ViewGroup by default;
- Motionevent. ACTION_SCROLL: non-touch sliding, MotionEvent contains relative vertical/horizontal scroll offset, this action is not a touch event.
RecyclerView onTouchEvent();
@Override
public boolean onTouchEvent(MotionEvent e) {
// Assigns slide events to OnItemTouchListener or provides interceptions for OnItemTouchListeners. True is returned if touch events are intercepted
if (findInterceptingOnItemTouchListener(e)) {
cancelScroll();
return true;
}
// Determine the sliding direction according to the layout direction
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();// Can you support horizontal sliding
final boolean canScrollVertically = mLayout.canScrollVertically();// Can you support vertical sliding
// Get a new VelocityTracker object to observe the sliding speed
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(e);
// Returns the operation being performed, without touchpoint index information. This is the event type, such as motionEvent.action_down
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();/ / the Action of the index
// Copy the event information to create a new event
final MotionEvent vtev = MotionEvent.obtain(e);
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
case MotionEvent.ACTION_DOWN: {// Press your finger down
mScrollPointerId = e.getPointerId(0);// Get the id of the first touchpoint associated with a specific touchpoint
// Record the X and Y coordinates of the Down event
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5 f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5 f);
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;// Indicates sliding along the horizontal axis
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;// Indicates sliding along the vertical axis
}
// Start a new nested scroll, if a collaborative parent View is found, and start the nested scroll
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
case MotionEvent.ACTION_POINTER_DOWN: {// Multiple finger press
// Update mScrollPointerId to respond only to recently pressed gesture events
mScrollPointerId = e.getPointerId(actionIndex);
// Update the most recent gesture coordinates
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5 f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5 f);
} break;
case MotionEvent.ACTION_MOVE: {// Move your finger
// Get the touchpoint subscript according to mScrollPointerId
final int index = e.findPointerIndex(mScrollPointerId);
// Calculate the offset dx, dy from the x, y generated by the move event
final int x = (int) (e.getX(index) + 0.5 f);
final int y = (int) (e.getY(index) + 0.5 f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if(mScrollState ! = SCROLL_STATE_DRAGGING) {// Is not touched to move state
boolean startScroll = false;
if (canScrollHorizontally) {// Horizontal sliding direction
if (dx > 0) {
dx = Math.max(0, dx - mTouchSlop);
} else {
dx = Math.min(0, dx + mTouchSlop);
}
if(dx ! =0) {
startScroll = true; }}if (canScrollVertically) {// Vertical sliding direction
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if(dy ! =0) {
startScroll = true; }}if(startScroll) { setScrollState(SCROLL_STATE_DRAGGING); }}// Being touched moves the state, where the sliding is really handled
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;//mReusableIntPair The sliding distance consumed by the parent view
mReusableIntPair[1] = 0;
//mScrollOffset indicates the scrolling position of RecyclerView
// Assigns a step of the nested pre-slide operation to the currently nested slide parent. True indicates that the parent View takes precedence over the slide event.
// if consumed, dx dx will subtract the distance consumed by the parent View
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
dx -= mReusableIntPair[0];// Subtract the distance consumed by the parent View
dx -= mReusableIntPair[1];
// Update nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Sliding has started to prevent the parent View from being blocked
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
// The final sliding effect
if (scrollByInternal(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
// Prefetch a ViewHolder from the cache
if(mGapWorker ! =null&& (dx ! =0|| dy ! =0)) {
mGapWorker.postFromTraversal(this, dx, dy); }}}break;
case MotionEvent.ACTION_POINTER_UP: {// Multiple fingers leave
// Select a new touch point to process the ending and reprocess the coordinates
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {// When the finger leaves, the sliding event ends
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
// Calculate the sliding speed
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
// The last X/Y slip speed
final float xvel = canScrollHorizontally ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// Handle inertial sliding
if(! ((xvel ! =0|| yvel ! =0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);// Set the sliding state
}
resetScroll();// Reset the slider
} break;
case MotionEvent.ACTION_CANCEL: {// Cancel the gesture to release various resources
cancelScroll();// Exit the slide
} break;
}
if(! eventAddedToVelocityTracker) { mVelocityTracker.addMovement(vtev); } vtev.recycle();// Recycle the sliding event for reuse. You can no longer touch the event by calling this method
return true;// Return true to RecyclerView to process events
}
Copy the code
ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL are the basic events of View. ACTION_POINTER_DOWN, The ACTION_POINTER_UP two events are related to multi-finger sliding.
There are three main things going on here,
- A slide event is assigned to OnItemTouchListener or intercepted by OnItemTouchListeners. True is returned if intercepted, indicating that the event is consumed.
- Second, initialize gesture coordinates, sliding direction, event information and other data;
- OnItemTouchListeners or OnItemTouchListeners do not consume the current event process.
There are a lot of details, let’s break them down case by case:
1. The Down event
case MotionEvent.ACTION_DOWN:{// Press your finger down
mScrollPointerId = e.getPointerId(0);// Get the id of the first touchpoint associated with a specific touchpoint
//1. Record the X and Y coordinates of the Down event
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5 f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5 f);
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;// Indicates sliding along the horizontal axis
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;// Indicates sliding along the vertical axis
}
//2. Start a new nested scroll, if a collaborative parent View is found, and start the nested scroll
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
Copy the code
The Down event first fetches the id of the first touchpoint. A Pointer is a touchpoint. Down is the start of a series of events.
- 1. Record the X and Y coordinates of the Down event.
- 2. Call
startNestedScroll()
A new nested slide is launched, and if the nested parent View is found, the nested slide is launched, that is, the event is processed.
2. Move events
case MotionEvent.ACTION_MOVE:{// Move your finger
// Get the touchpoint subscript according to mScrollPointerId
final int index = e.findPointerIndex(mScrollPointerId);
//1. Calculate offset dx, dy based on x, y generated by move event
final int x = (int) (e.getX(index) + 0.5 f);
final int y = (int) (e.getY(index) + 0.5 f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if(mScrollState ! = SCROLL_STATE_DRAGGING) {// Is not touched to move state
boolean startScroll = false;
if (canScrollHorizontally) {// Horizontal sliding direction......}if (canScrollVertically) {// Vertical sliding direction......}// Set the sliding state, SCROLL_STATE_DRAGGING indicates sliding
if (startScroll) setScrollState(SCROLL_STATE_DRAGGING);
}
// Being touched moves the state, where the sliding is really handled
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;//mReusableIntPair The sliding distance consumed by the parent view
mReusableIntPair[1] = 0;
//2. Assign a step of the nested pre-slide operation to the currently nested scroll parent. If true, the parent View takes precedence over the slide event.
// if it consumes, dx dy will subtract the distance consumed by the parent View, mScrollOffset indicates the scrolling position of RecyclerView
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
dx -= mReusableIntPair[0];// Subtract the distance consumed by the parent View
dy -= mReusableIntPair[1];
// Update nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Start sliding to prevent the parent View from being blocked
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
//3. The final implementation of the scrolling effect
if (scrollByInternal(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//4. Prefetch a ViewHolder from cache
if(mGapWorker ! =null&& (dx ! =0|| dy ! =0)) {
mGapWorker.postFromTraversal(this, dx, dy); }}}break;
Copy the code
Move event is the core of the sliding event processing, the code is relatively long, but the structure is simple, mainly divided into the following steps:
- 1. Calculate offset dx and dy according to x and y generated by move event;
- 2.
dispatchNestedPreScroll()
Dispatch a step that asks the parent View if it needs to handle the sliding event first. If so, dx and dy will subtract the distance consumed by the parent View. - 3. Determine the sliding direction and call
scrollByInternal()
Finally achieve the scrolling effect; - 4. Call
mGapWorker.postFromTraversal()
Prefetch a ViewHolder from the RecyclerView cache.
ScrollByInternal () is the final implementation of sliding effect, which will be analyzed in detail later. GapWorker prefetchviewholder by adding Runnable to RecyclerView task queue. Final call RecyclerView. Recycler tryGetViewHolderForPositionByDeadline get ViewHolder (), it is the core of the whole RecyclerView recycling reuse caching mechanism method. Here is not a detailed analysis, RecyclerView recycle cache mechanism detailed explanation “hope to provide you with help.
3. Up event
case MotionEvent.ACTION_UP: {// When the finger leaves, the sliding event ends
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
//1. Calculate the current sliding speed according to the past points
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
// The last X/Y slip speed
final float xvel = canScrollHorizontally ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// Handle inertial sliding
if(! ((xvel ! =0|| yvel ! =0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);// Set the sliding state
}
resetScroll();//2. Reset slider
} break;
Copy the code
The Up event ends when the finger leaves. Two main things were done:
- 1. By
computeCurrentVelocity()
Compute the sliding speed and compute the final sliding speed of the X and Y axes,fling()
It deals with inertial sliding; - 2. Set the sliding state and reset the sliding information after the inertia sliding.
First, computeCurrentVelocity() is used to calculate the sliding speed and the final sliding speed of X and Y axes. If the final sliding speed is greater than the given value of the system when lifting, the inertia is maintained and then the sliding distance is maintained. Finally, the nested sliding View is notified to finish sliding and the data is reset. Fling () is the core method for handling inertial slides, as discussed below.
4. Cancel the event
case MotionEvent.ACTION_CANCEL:{// Cancel the gesture to release various resources
cancelScroll();// Exit the slide
} break;
private void cancelScroll(a) {
//1. Reset slider, whether resource
resetScroll();
//2. Set the sliding state to no sliding state
setScrollState(SCROLL_STATE_IDLE);
}
Copy the code
The Cancel event indicates that the gesture event has been cancelled, resetting the sliding status and other information. Two main things were done:
- 1.
resetScroll()
Stop nesting slides in progress, freeing resources - 2. Set the sliding status to
SCROLL_STATE_IDLE
There’s no sliding.
When the event is consumed by the parent View, it will respond to the Cancel event. For example, when RecyclerView receives the Down event, but is subsequently intercepted by the parent View, the RecyclerView will respond to the Cancel event.
5. Pointer_Down events
case MotionEvent.ACTION_POINTER_DOWN:{// Multiple finger press
// Update mScrollPointerId to respond only to recently pressed gesture events
mScrollPointerId = e.getPointerId(actionIndex);
// Update the most recent gesture coordinates
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5 f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5 f);
} break;
Copy the code
The Pointer_Down event mainly updates the mScrollPointerId and the pressed coordinates immediately when multiple fingers are pressed. Respond to the new gesture, no longer respond to the old gesture, all events and coordinates to the new event and coordinates prevail.
Note: the RecyclerView does not respond to multiple finger swiping, but RecyclerView does not respond to the old finger gesture, but responds to the latest finger gesture when the old finger is not released and another new finger is pressed.
6. Pointer_Up events
case MotionEvent.ACTION_POINTER_UP:{// Multiple fingers leave
// Select the latest coordinate point to process the outcome, reprocess the coordinates
onPointerUp(e);
} break;
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);
mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5 f);
mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5 f); }}Copy the code
The Pointer_Up event selects the latest pointer to handle the outcome when multiple fingers leave. OnPointerUp () determines whether the leaving event coordinate ID is consistent with the current sliding coordinate ID, if so, the gesture coordinate and the current coordinate point ID will be updated.
2.2. Sliding process
RecyclerView’s onTouchEvent() method is used to change the event type of the RecyclerView. The last article combined with the source code of LinearLayoutManager analyzed the drawing process of RecyclerView, here also take the vertical direction of LinearLayoutManager as an example to analyze the vertical direction of RecyclerView sliding, sliding in other ways are all changes. Get the slide direction when you start responding to onTouchEvent() :
// Determine the sliding direction according to the layout direction
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();// Can you support horizontal sliding
final boolean canScrollVertically = mLayout.canScrollVertically();// Can you support vertical sliding
Copy the code
Above is obtained through LinearLayoutManager whether can slide in horizontal and vertical direction, here the callback mLayout. CanScrollHorizontally () and mLayout canScrollVertically () method, So if canScrollHorizontally = true, I can swipe left and right, and if canScrollHorizontally = true, I can swipe up and down.
@Override
public boolean canScrollHorizontally(a) {
return mOrientation == HORIZONTAL;// The linear direction is horizontal, can slide horizontally
}
@Override
public boolean canScrollVertically(a) {
return mOrientation == VERTICAL;// The linear direction is vertical direction, can slide vertically
}
Copy the code
1. Plain sliding
As the Move event analysis above knows, calculate the distance of the slide in ACTION_MOVE and then call scrollByInternal() to handle the itemView sliding with the gesture. The core method is scrollByInternal() :
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0; int unconsumedY = 0;
int consumedX = 0; int consumedY = 0;
//1. Use deferred changes to avoid crashes that may be caused by adapter changes during sliding
consumePendingUpdateOperations();
if(mAdapter ! =null) {
//2. Slide step
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
//3. Assign a step of the nested pre-slide operation to the currently nested slide parent View. If true, the parent View takes precedence over the slide event.
// if consumed, dx dy subtracts the distance consumed by the parent View
dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
TYPE_TOUCH, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;
// Update the last touch coordinates taking into account the slip offset
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
//4. Slide callback
if(consumedX ! =0|| consumedY ! =0) {
dispatchOnScrolled(consumedX, consumedY);
}
// Whether there is sliding consumption
returnconsumedNestedScroll || consumedX ! =0|| consumedY ! =0;
}
Copy the code
The above code does four main things:
- 1.
consumePendingUpdateOperations()
Delay is used to avoid crashes that may be caused by adapter changes during a slide, because the slide assumes that no data has changed when in fact the data has changed; - 2.
scrollStep()
The core slide step is handed over to the layout manager to handle its own slide; - 3. The nested sliding mechanism is still used to notify the parent View to preferentially handle sliding events after its own sliding is completed;
- 4.
dispatchOnScrolled()
Notify the slide callback listener of RecyclerView.
ScrollStep () is handled its sliding method, through the dx, dy slide RecyclerView, horizontal slip. Call the mLayout scrollHorizontallyBy (), vertical sliding. Call the mLayout scrollVerticallyBy ();
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
if(dx ! =0) {
// Slide the dx pixel horizontally in the screen coordinates and return the distance moved, default is not moved, return 0
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if(dy ! =0) {
// Slide the dy pixel vertically in the screen coordinates and return the distance moved, default is not moved, return 0consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); }}Copy the code
The final sliding distance is handled by LayoutManager in the sliding function. Here’s the vertical sliding scrollVerticallyBy() :
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {// If it is horizontal, the vertical sliding distance is 0, that is, no sliding
return 0;
}
return scrollBy(dy, recycler, state);
}
Copy the code
ScrollVerticallyBy () is a vertical slide, if the linear direction is HORIZONTAL, the slide distance is 0, that is, no slide, otherwise scrollBy() is called to achieve a vertical slide:
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || delta == 0) {// Do not slide if there is no data or the slide distance is 0
return 0;
}
1. Update the layout status
updateLayoutState(layoutDirection, absDelta, true, state);
//2. First call fill() to bring in the view that has been slid in, and reclaim the view that has been slid out to return the space of the current layout view
final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
// Calculate the sliding distance
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
//3. Add offsets to all child views and move the View position according to the calculated sliding distance
mOrientationHelper.offsetChildren(-scrolled);/ / move
mLayoutState.mLastScrollDelta = scrolled;// Record the sliding distance
return scrolled;
}
Copy the code
These two methods do three main things,
- 1. By
updateLayoutState()
[Fixed] some states, such as where the stroke point is, whether there is animation, etc. - 2. By
fill()
First check which views are out of bounds, recycle them, then refill the new view and return the offset of the fill; - Through 3.
offsetChildren()
Add offsets to all child views and move the View’s position according to the sliding distance.
The logic for scrollBy() to handle sliding is to update the state of the layout, then call fill() to return the fill distance, and if there is a slide distance, the View layout is put in, and if a View is completely removed from the screen, it is recycled into the cache. OffsetChildren () is called to offset all child Views.
Note: The sliding event does not re-request the layout, it does not re-request onLayoutChildren(), updates to the layout are either re-retrieved from the cache by fill() or by creating an itemView to populate the screen.
OrientationHelper () : offsetChildren() : offsetChildren() : offsetChildren() : offsetChildren() : offsetChildren() : offsetChildren() :
// Move all child views by the given distance
public abstract void offsetChildren(int amount);
Copy the code
In LinerLayoutManager source code implementation of OrientationHelper class and implementation of abstract methods:
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {
@Override
public void offsetChildren(int amount) {
mLayoutManager.offsetChildrenVertical(amount);// Offset all child views vertically to append to RecyclerView}}; }Copy the code
OffsetChildrenVertical (int dx) offsetChildrenVertical(int dx) offsetChildrenVertical(int dx) offsetChildrenVertical(int dx)
public void offsetChildrenVertical(@Px int dy) {
if(mRecyclerView ! =null) { mRecyclerView.offsetChildrenVertical(dy); }}Copy the code
RecyclerView offsetChildrenVertical() :
public void offsetChildrenVertical(@Px int dy) {
// Get the number of RecyclerView itemViews
final int childCount = mChildHelper.getChildCount();
// Swipe through all itemViews by calling View offsetTopAndBottom()
for (int i = 0; i < childCount; i++) {
mChildHelper.getChildAt(i).offsetTopAndBottom(dy);// How many pixels to move the view}}Copy the code
Finally, I found the source code of the core moving sub-view: iterating through all itemViews, and finally sliding through each sub-view by calling the offsetTopAndBottom() or offsetLeftAndRight() method of the underlying View. Get the total number of ItemViews, and then move each itemView by the specified distance dy by traversing.
General sliding summary: In RecyclerView Move touch event dispatch sliding event responsescrollByInternal()
Method that handles nested sliding of the parent View and actually calls the LayoutManagerscrollHorizontallyBy()
orscrollVerticallyBy()
Method to calculatescrollBy()
In thefill()
Filling the layout handles the actual sliding distance, iterating through all itemViews, and finally calling the underlying View’s through each child ViewoffsetTopAndBottom()
oroffsetLeftAndRight()
Method to achieve sliding.
2. Inertial sliding
We slide the list quickly and then release our finger. The list continues to slide inertia for a while, RecyclerView inertia slide (), while onTouchEvent() processes ACTION_UP events:
case MotionEvent.ACTION_UP: {// When the finger leaves, the sliding event ends
mVelocityTracker.addMovement(vtev);
//1. Calculate the current sliding speed according to the past points
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
// The last X/Y slip speed
final float xvel = canScrollHorizontally ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// Handle inertial sliding
if(! ((xvel ! =0|| yvel ! =0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);// Set the sliding state
}
resetScroll();//2. Reset slider
} break;
Copy the code
First, computeCurrentVelocity() is used to calculate the sliding speed and the final sliding speed of X and Y axes. If the final speed is greater than the given value of the system when lifting, the inertia will be maintained and the sliding distance will be maintained. Finally, the nested sliding View will be notified that the sliding has ended and the sliding information will be reset. Inertia sliding core method Fling () :
public boolean fling(int velocityX, int velocityY) {
//1. Can you slide horizontally and vertically
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if(! canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { velocityX =0;// If you cannot slide horizontally, or if the slide speed is less than the system's slide speed, the horizontal slide speed is set to 0
}
if(! canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { velocityY =0;// If you cannot slide vertically, or if the slide speed is less than the system slide speed, the vertical slide speed is set to 0
}
// No sliding speed, returns false, does not handle inertial sliding
if (velocityX == 0 && velocityY == 0) return false;
// Whether the parent View handles nested pre-inertial sliding
if(! dispatchNestedPreFling(velocityX, velocityY)) {final boolean canScroll = canScrollHorizontal || canScrollVertical;
dispatchNestedFling(velocityX, velocityY, canScroll);
//2. The client handles inertial sliding as required by the developer
if(mOnFlingListener ! =null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
if (canScroll) {// If you can slide
startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH);// Start nesting slides
//3.RecyclerView process its own inertia sliding
mViewFlinger.fling(velocityX, velocityY);
return true; }}return false;
}
Copy the code
Fling () does three things:
- 1. According to the sliding direction and the sliding speed compared with the system speed, judge whether it can slip inertia;
- 2. The client handles inertia sliding and passes as required by the developer
OnFlingListener.onFling()
Method To judge whether RecyclerView is good to deal with the inertia motion required by developers, and to decide whether it can deal with inertia sliding; - 3.RecyclerView itself to handle inertia sliding, call ViewFlinger
fling()
Methods.
RecyclerView is a Runnable class inside of RecyclerView. Then The Fling () of ViewFlinger is RecyclerView.
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);// Set the scroll state to inertial slide
mOverScroller = new OverScroller(getContext(), sQuinticInterpolator);
// Start sliding based on a swinging gesture, the distance covered depends on the initial speed.
mOverScroller.fling(0.0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
// Make Runnable run on the next animation step, Runnable will run on the user interface thread.
postOnAnimation();
}
Copy the code
Moverscroll.fling () just calculates the parameters for inertial sliding, and finally calls postOnAnimation(), which finally calls back to ViewFlinger’s run method:
@Override
public void run(a) {· · · · · ·final OverScroller scroller = mOverScroller;
//1. Update the sliding position information to determine whether the current sliding is complete. True indicates that the sliding is not complete
if (scroller.computeScrollOffset()) {
final int x = scroller.getCurrX();
final int y = scroller.getCurrY();
// Calculate the rolling distance
int unconsumedX = x - mLastFlingX;
int unconsumedY = y - mLastFlingY;
mLastFlingX = x;
mLastFlingY = y;
int consumedX = 0;
int consumedY = 0; · · · · · ·if(mAdapter ! =null) {// Local slide
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
//2. Slide step by dX, dY slide RecyclerView
scrollStep(unconsumedX, unconsumedY, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX -= consumedX;
unconsumedY -= consumedY;
}
// slide after nesting
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// Whether the parent View handles nested sliding events
dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, null,
TYPE_NON_TOUCH, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
boolean scrollerFinishedX = scroller.getCurrX() == scroller.getFinalX();
boolean scrollerFinishedY = scroller.getCurrY() == scroller.getFinalY();
// Whether the slide is complete (the slide ends or the x,y distance is complete or no further slide is possible)
final booleandoneScrolling = scroller.isFinished() || ((scrollerFinishedX || unconsumedX ! =0) && (scrollerFinishedY || unconsumedY ! =0));
//4. End of slide
if(! smoothScrollerPending && doneScrolling) {if(getOverScrollMode() ! = View.OVER_SCROLL_NEVER) {final int vel = (int) scroller.getCurrVelocity();
int velX = unconsumedX < 0 ? -vel : unconsumedX > 0 ? vel : 0;
int velY = unconsumedY < 0 ? -vel : unconsumedY > 0 ? vel : 0;
absorbGlows(velX, velY);
}
if(ALLOW_THREAD_GAP_WORK) { mPrefetchRegistry.clearPrefetchPositions(); }}else {
//3. Otherwise continue to slide (recursively execute the run method until the slide ends)
postOnAnimation();
// Prefetch ViewHolder(cache fetch or create)
if(mGapWorker ! =null) {
mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY); }}}// re-slide
if (mReSchedulePostAnimationCallback) {
internalPostOnAnimation();
} else {// Set the slide end statesetScrollState(SCROLL_STATE_IDLE); stopNestedScroll(TYPE_NON_TOUCH); }}Copy the code
There are three main things it does:
- 1. Update the sliding position information to determine whether the current sliding is complete. True indicates that the sliding is not complete;
- 2. Calculate the relevant information of sliding distance and call back
scrollStep()
By dX, dY sliding RecyclerView; - 3. If the sliding is not complete, perform the operation
postOnAnimation()
Recursive run method until the slide ends; - 4. After the slide ends, clear the data and set the slide end state.
Inertial sliding summary: RecyclerView responseonTouchEvent()
According to the final sliding speed, it can judge whether there is inertia sliding. If there is inertia sliding, it will passfling()
First deal with the developers required to deal with inertia sliding, otherwise directly RecyclerView itself to deal with inertia sliding, in ViewFlingerfling()
Calculate the slide related coordinate data information, and then inpostOnAnimation()
In the callbackrun()
Handle slides, which are also calledscrollStep()
Complete the slide, and recursively execute if the slide does not endpostOnAnimation()
Methods the callbackrun()
Straight slide done.
2.3. Summary of sliding principle
RecyclerView sliding event processing is still through onTouchEvent() touch event response, calculation update touch coordinates and sliding direction and other related information, processing parent View nested sliding, sliding event response scrollByInternal() method, Actually call LayoutManager’s scrollHorizontallyBy() or scrollVerticallyBy() method to calculate the actual sliding distance that the fill() fills the layout while processing in scrollBy(), Finally, RecyclerView iterates through all itemViews, and finally through each child View calls the bottom View offsetTopAndBottom() or offsetLeftAndRight() method to achieve sliding.
The sliding flow chart of RecyclerView is as follows (double-click to open higher clarity) :
Pay attention and don’t get lost
Well everyone, that’s all for this article. Thank you very much for reading it. I am Suming, thank you for your support and recognition, your praise is the biggest motivation for my creation. Landscape has to meet, we will see you in the next article!
If there are any mistakes in this blog, please comment, thank you very much!