More than technology, the article has material, pay attention to the public number nine heart said, a week of high quality good article, and nine heart side by side in dailang road.
preface
RecyclerView
This is the first chapter in a series designed to help Android developers improve their awareness of RecyclerView.
RecyclerView
Has been out for a long time, a lot of developers forRecyclerView
Has been in the palm of his hand. Here is one using a grid layoutRecyclerView
: But, for example,RecyclerView
This star control understanding only stays in the use of the degree, is obviously not able to let us become a senior engineer. If you’ve seenRecyclerView
Package source code, then you should be as complex as my mood, light aRecyclerView.class
The source code of the file is as much as 13,000 lines.
For the source reading, I agree with Guo Shen in Glide source analysis said:
Peel off the threads and stop when you reach them. Identify a function point and then analyze how that function point is implemented. But as long as you go after the realization logic of the subject, do not try to understand every line of code, it is easy to fall into the black hole of thinking, and deeper and deeper.
So, WHEN I read the RecyclerView source code to determine their own want to understand the function point:
- The data is transformed into concrete subviews.
- View recycling mode.
- Layout diversity reasons.
- Layout animation diversity reasons.
Reading posture: I chose the version of 27.1.1 RecyclerView, do not know what reason, I point into the 28.0.0 version of RecyclerView library to view the recyclerView. class code, although the class shortened to 7000 lines, but notes and other problems, I had to use a different version of the RecyclerView library.
To get to the bottom of this, there is nothing that can’t be solved in one run, and if there is, it’s the second run.
directory
RecyclerView use and introduction
Using the LinearLayoutManager as an example, let’s look at how RecyclerView can be used:
RecyclerView mRecyclerView = findViewById(R.id.recycle); / / set layout mRecyclerView. SetLayoutManager (new LinearLayoutManager (this, LinearLayoutManager. VERTICAL, false)); Adapter<VH extends recyclerView.viewholder > MainAdapter mAdapter = new MainAdapter(); mRecyclerView.setAdapter(mAdapter); Method of adding line / / / / mRecyclerView addItemDecoration (); / / set up the layout method of animation, you can customize / / mRecyclerView setItemAnimator ();Copy the code
And the role of various parts of RecyclerView:
The main class | role |
---|---|
LayoutManager |
To be responsible for theRecyclerView The childView Layout, commonly used areLinearLayoutManager (Linear layout), andGridLayoutManager (grid layout) andStaggeredGridLayoutManager (waterfall layout), etc. |
Adapter |
This class is responsible for turning the data into a view. |
ItemAnimator |
Subview animation,RecyclerView There are default subview animations, or custom implementations. |
ItemDecoration |
Dividing line, need custom implementation. |
These are the ones we useRecyclerView There is another important but not directly used class: |
|
The main class | role |
: – : | :– |
Recycler |
To be responsible for theViewHolder Recycling and provision. |
## 2, source code analysis | |
### 1. RecyclerView three major workflow | |
RecyclerView Source code so much, we first according to the use of the route for analysis. |
|
##### 1.1 constructor | |
Normally, we use it in a layout fileRecyclerView , so our entrance becomes: |
|
` ` ` | |
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { |
this(context, attrs, 0);
Copy the code
}
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); / /… Omit some instance initialization
if (attrs ! = null) { int defStyleRes = 0; TypedArray a = context.obtainStyledAttributes(attrs, styleable.RecyclerView, defStyle, defStyleRes); String layoutManagerName = a.getString(styleable.RecyclerView_layoutManager); / /... The only interesting thing here is to see if the layout file specifies LayoutManager a.recycle(); this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); / /... } else { // ... } / /...Copy the code
}
Since we can use 'app:layoutManager' in the 'RecyclerView' layout file to specify 'layoutManager', if specifying a specific 'layoutManager', RecyclerView#createLayoutManager {RecyclerView#createLayoutManager {RecyclerView#createLayoutManager} ##### 1.2 set LayoutManager and Adapter research custom View, the fastest research method is directly View 'onMeasure', 'onLayout' and 'onDraw' three methods, research 'RecyclerView' is also the same. So we've got layout files, and after that, we get a RecyclerView in our Activity or somewhere else, and then we move on, We will set 'LayoutManager' (if not set in the layout file), 'Adapter' and possibly 'ItemDecoration' for the 'RecyclerView', RecyclerView#requestLayout 'RecyclerView# RecyclerView' RecyclerView#setLayoutManager 'RecyclerView#setLayoutManager'Copy the code
public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) { if (layout ! = this.mlayout) {// stop scrolling this.stopScroll(); if (this.mLayout ! = null) {// mLayout is empty because it is set for the first time //… } else {this.mrecycler.clear ();} else {this.mrecycler.clear (); } this.mChildHelper.removeAllViewsUnfiltered(); this.mLayout = layout; if (layout ! = null) {/ / to set up this new LayoutManager mLayout. SetRecyclerView (this); if (this.mIsAttached) { this.mLayout.dispatchAttachedToWindow(this); } } this.mRecycler.updateViewCacheSize(); // Focus on notifying the interface to rearrange and redraw this.requestLayout(); }}
RecyclerView#requestLayout 'RecyclerView#requestLayout' No, because the Adapter is empty in the RecyclView. If the Adapter is empty, there's no data. So what's the point of looking at an empty view? So, we also need to look at setting adapter's 'RecyclerView#setAdapter' method:Copy the code
Public void setAdapter(@nullable recyclerView. Adapter Adapter) {this.setLayoutFrozen(false); // The focus method is this.setAdapterInternal(Adapter, false, true); this.processDataSetCompletelyChanged(false); This.requestlayout (); this.requestLayout(); }
RecyclerView#setAdapterInternal 'RecyclerView#setAdapterInternal'Copy the code
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, Boolean compatibleWithPrevious, Boolean removeAndRecycleViews) { if (this.mAdapter ! = null) {// Set mAdapter to null the first time Reason not to enter the code block / / data is mainly for old mAdapter remove listeners registered this. MAdapter. UnregisterAdapterDataObserver (enclosing mObserver); this.mAdapter.onDetachedFromRecyclerView(this); } if (! CompatibleWithPrevious | | removeAndRecycleViews) {/ / change the adapter to remove all child View this. RemoveAndRecycleViews (); } this.mAdapterHelper.reset(); RecyclerView.Adapter oldAdapter = this.mAdapter; this.mAdapter = adapter; if (adapter ! = null) {/ / new adapter registration data listener adapter. RegisterAdapterDataObserver (enclosing mObserver); adapter.onAttachedToRecyclerView(this); } if (this.mLayout ! = null) { this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter); } this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious); this.mState.mStructureChanged = true; }
It can be seen that the code above is mainly aimed at ` Adapter ` change situation to make some changes, ` RecyclerView. AdapterDataObserver ` interface is the data change, when the data in the Adapter to increase bowdlerize will invoke the interface implementation class, The observer pattern is indicated by the naming of the interface and the registration and unregistration operations. Once the 'LayoutManager' and 'Adapter' are set up, you can get right to the point. ##### 1.3 The first step of onMeasure View workflow:Copy the code
protected void onMeasure(int widthSpec, int heightSpec) { if (this.mLayout == null) { this.defaultOnMeasure(widthSpec, heightSpec); } else {// LinearLayoutManager#isAutoMeasureEnabled is True // GridLayoutManager inherits the child LinearLayoutManager IsAutoMeasureEnabled also to true / / in this case, we mainly analyze this. MLayout. IsAutoMeasureEnabled () to true scenario if (! this.mLayout.isAutoMeasureEnabled()) { // … Else {int widthMode = measurespec.getMode (widthSpec); int heightMode = MeasureSpec.getMode(heightSpec); / /… OnMeasure (this.mrecycler, this.mstate, widthSpec, heightSpec); Boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; / / if the current layout of RecyclerView way is to set up a specific height to width or Match_Parent or mAdapter null is returned directly if (measureSpecModeIsExactly | | this. MAdapter = = null) { return; } if (this.mState.mLayoutStep == State.STEP_START) { this.dispatchLayoutStep1(); } this.mLayout.setMeasureSpecs(widthSpec, heightSpec); this.mState.mIsMeasuring = true; this.dispatchLayoutStep2(); this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); if (this.mLayout.shouldMeasureTwice()) { this.mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), MeasureSpec.EXACTLY)); this.mState.mIsMeasuring = true; this.dispatchLayoutStep2(); this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); }}}}
Obviously, from the above code we can conclude that if ** 'measureSpecModeIsExactly' is true or 'Adapter' is empty, we will prematurely end the 'onMeasure' measurement process. ** If you have seen 'View workflow' you are familiar with 'SpecMode', when will 'SpecMode' be 'EXACITY'? Take 'RecyclerView' as an example. Normally, if the width of 'RecyclerView' is a specific value or 'Match_Parent', then its' SpecMode 'is largely' EXACITY '. MeasureSpecModeIsExactly 'is' true' needs to ensure that the 'SpecMode' of both height and width is' EXACITY '. Of course, the 'SpecMode' of ** 'View is also related to the parent layout **. If your code RecyclerView does not use 'Wrap_Content', then most scenarios of 'RecyclerView' long and wide 'SpecMode' are 'EXACITY', I say so, RecyclerView#dispatchLayoutStep1 and RecyclerView#dispatchLayoutStep2 Since they are also executed in another workflow, 'onLayout', we will explain them in 'onLayout'. ##### 1.4 Step 2 of the onLayout View workflow:Copy the code
protected void onLayout(Boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(“RV OnLayout”); this.dispatchLayout(); TraceCompat.endSection(); this.mFirstLayoutComplete = true; }
void dispatchLayout() { if (this.mAdapter == null) { // … } else if (this.mLayout == null) { // … } else { this.mState.mIsMeasuring = false; If (this.mstate.mLayoutStep == STEP_START) {this.dispatchLayoutStep1(); this.mLayout.setExactMeasureSpecsFrom(this); this.dispatchLayoutStep2(); } else if (! this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) { this.mLayout.setExactMeasureSpecsFrom(this); } else { this.mLayout.setExactMeasureSpecsFrom(this); this.dispatchLayoutStep2(); } this.dispatchLayoutStep3(); }}
In the 'mState' instance initialization, 'mstate. mLayoutStep' defaults to 'STEP_START', 'RecyclerView#dispatchLayoutStep1' method must be enter:Copy the code
Private void dispatchLayoutStep1() {mvieWinFoStore.clear (); / / determine mState. MRunSimpleAnimations and mState. MRunPredictiveAnimations / /… / / preliminary layout state with mState mRunPredictiveAnimations related mState. MInPreLayout = mState. MRunPredictiveAnimations; / /… if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout int count = mChildHelper.getChildCount(); for (int i = 0; i < count; ++i) { // … // Store subview location information… mViewInfoStore.addToPreLayout(holder, animationInfo); }} the if (mState mRunPredictiveAnimations) {/ / actually, I don’t understand the meaning of PreLayout layout, released to see / / Step 1: run PreLayout: This will use the old positions of items. The layout manager // is expected to layout everything, even removed items (though not to add removed // items back to the container). This gives the pre-layout position of APPEARING views // which come into existence as part of the real layout.
// Temporarily disable flag because we are asking for previous layout mLayout.onLayoutChildren(mRecycler, mState); for (int i = 0; i < mChildHelper.getChildCount(); ++i) { //... if (! mViewInfoStore.isInPreLayout(viewHolder)) { // ... if (wasHidden) { recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); } else { mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); } } } // we don't process disappearing list because they may re-appear in post layout pass. clearOldPositions(); } else { clearOldPositions(); } // mState.mLayoutStep = State.STEP_LAYOUT;Copy the code
}
private void processAdapterUpdatesAndSetAnimationFlags() { // … / / mFirstLayoutComplete will be done for the first time in RecyclerView onLayout into True Boolean animationTypeSupported = mItemsAddedOrRemoved | | mItemsChanged; mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator ! = null && (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && (! mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && ! mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled(); }
. We need to focus on ` mState mRunSimpleAnimations ` and ` mState. MRunPredictiveAnimations ` to true timing, from the code point of view, these two attributes to true must exist ` mItemAnimator `, Does that mean the child View animation practitioners ` mItemAnimator `, in addition, ` mViewInfoStore. AddToPreLayout (holder, animationInfo); 'Also notice that' ViewInfoStore 'helps' RecyclerView' record the position information and state of 'ViewHolder' neutron View. RecyclerView#dispatchLayoutStep2Copy the code
private void dispatchLayoutStep2() { // … This.mstate. mInPreLayout = false; / / the actual layout to the LayoutManager enclosing mLayout. OnLayoutChildren (enclosing mRecycler, enclosing mState); / /… / / if there’s any animation this. MState. MRunSimpleAnimations = this. MState. MRunSimpleAnimations && enclosing mItemAnimator! = null; // Change the State to play the animations STEP_ANIMATIONS-4 this.mstate.mlayoutStep = state.step_animations; / /… }
RecyclerView#dispatchLayoutStep2 in the 'RecyclerView' method we can see that the 'RecyclerView' itself does not realize to the child 'View' layout, but the layout method to the 'LayoutManager', The in-depth study of 'LayoutManager' will be discussed in a future blog. Strike while the iron is hot, we view the 'RecyclerView#dispatchLayoutStep3', code more, simplified as follows:Copy the code
private void dispatchLayoutStep3() { this.mState.assertLayoutStep(State.STEP_ANIMATIONS); / /… Omit this.mstate.mlayoutstep = state.step_start; if (this.mState.mRunSimpleAnimations) { for (int i = this.mChildHelper.getChildCount() – 1; i >= 0; –i) { // … Omit down / / summary is two steps: / / 1. Add the layout of the real information this. MViewInfoStore. AddToPostLayout (holder, animationInfo); } / / 2. Each animation this execution. MViewInfoStore. Process (enclosing mViewInfoProcessCallback); } / /… This clear information. MViewInfoStore. The clear (); }
When calling 'ViewInfoStore#process' that executes the animation, you can see that the argument 'mViewInfoProcessCallback' is put in. As the name suggests, this is a callback interface, so I guess the actual execution of the animation should be in the method that implements the interface, however, Again, we need to see how the animation in 'ViewInfoStore' works:Copy the code
void process(ProcessCallback callback) { for (int index = mLayoutHolderMap.size() – 1; index >= 0; index –) { final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); final InfoRecord record = mLayoutHolderMap.removeAt(index); if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) { // Appeared then disappeared. Not useful for animations. callback.unused(viewHolder); } else if ((record.flags & FLAG_DISAPPEARED) ! = 0) { // Set as “disappeared” by the LayoutManager (addDisappearingView) if (record.preInfo == null) { // similar to appear disappear but happened between different layout passes. // this can happen when the layout manager is using auto-measure callback.unused(viewHolder); } else { callback.processDisappeared(viewHolder, record.preInfo, record.postInfo); } } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) { // Appeared in the layout but not in the adapter (e.g. entered the viewport) callback.processAppeared(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) { // Persistent in both passes. Animate persistence callback.processPersistent(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_PRE) ! = 0) { // Was in pre-layout, never been added to post layout callback.processDisappeared(viewHolder, record.preInfo, null); } else if ((record.flags & FLAG_POST) ! = 0) { // Was not in pre-layout, been added to post layout callback.processAppeared(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_APPEAR) ! = 0) { // Scrap view. RecyclerView will handle removing/recycling this. } else if (DEBUG) { throw new IllegalStateException(“record without any reasonable flag combination:/”); } // Release record Inforecord. recycle(record); }}
Interface ProcessCallback {void processLight (ViewHolder var1, @nonnull ItemHolderInfo var2, @Nullable ItemHolderInfo var3); void processAppeared(ViewHolder var1, @Nullable ItemHolderInfo var2, ItemHolderInfo var3); void processPersistent(ViewHolder var1, @NonNull ItemHolderInfo var2, @NonNull ItemHolderInfo var3); void unused(ViewHolder var1); }
The previously stored 'InfoRecords' related to the' ViewHolder 'position state are retrieved one by one, and the' ViewHolder 'and' InfoRecord 'are handed over to' ProcessCallback 'and, as expected, Process #process #process #process #process #process #process #process #process #process #process #process #processCopy the code
this.mViewInfoProcessCallback = new ProcessCallback() { // … Public void processAppeared(RecyclerView.viewholder ViewHolder, recyclerView.viewholder) RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo info) { RecyclerView.this.animateAppearance(viewHolder, preInfo, info); } / /… };
void animateAppearance(@NonNull RecyclerView.ViewHolder itemHolder, @Nullable RecyclerView.ItemAnimator.ItemHolderInfo preLayoutInfo, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo postLayoutInfo) { itemHolder.setIsRecyclable(false); if (this.mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { this.postAnimationRunner(); }}
For space's sake, I only show one method 'ProcessCallback' implemented in 'ProcessCallback', in which it calls the 'RecyclerView#animateAppearance' method, Eventually, to the task of animation ` RecyclerView. ItemAnimator `, ` RecyclerView. ItemAnimator ` can be realized by the custom. It is important to note that some delete or add operations will eventually notify the interface to redraw by using the adapter's method of notifies delete or add. In the process of 'onLayout', 'RecyclerView' assigns the task of subview layout to 'LayoutMananger'. Similarly, the subview animation is not done by 'RecyclerView' itself. Animation task was to ` RecyclerView. ItemAnimator `, it also solved we initially put forward two questions: 1. Reason 2. Layout diversity layout animation diversity As for ` LayoutManager ` and ` RecyclerView. ItemAnimator ` deeper discussion, I will be in the back of the blog. ##### 1.5 onDraw 'RecylcerView' = 'ItemDecoration'; ##### 1.5 onDraw 'RecylcerView' = 'ItemDecoration';Copy the code
public void onDraw(Canvas c) { super.onDraw(c); int count = this.mItemDecorations.size(); for (int i = 0; i < count; ++i) { ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState); }}
The subview is actually implemented in 'ViewGroup#dispatchDraw', which I won't discuss here. If you don't understand, it doesn't matter, 'RecyclerView' in the three major engineering processes probably do the following things:! "The View of the three process] (https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c3b25c5ffd849e889fea6f280e0fd49~tplv-k3u1fbpfcp-zoom-1.im In the above part, we have a brief look at the three processes of **RecyclerView drawing and the tasks undertaken by 'LayoutManager' and 'ItemAnimator' **. Obviously, we're ignoring Adapter and cache management. Let's focus on those two. In the RecyclerView#dispatchLayoutStep2 method, the task of assigning subviews to the LayoutManager is given:Copy the code
mLayout.onLayoutChildren(mRecycler, mState);
A brief introduction to the work of 'layoutman #onLayoutChildren' : 1. If there are still children 'views' in the current' RecyclerView ', remove all of them and add the removed 'ViewHolder' to 'RecyclerView'. 2. Get a child View through 'Recycler' at a time. 3. Repeat 2 until the subview is filled with 'RecyclerView'. Although that's simple, the actual work of LayoutManager can be a lot more complicated. How does it work to have Recycler present? Let's find out. ##### 2.1 Important components of Recycler | | | to participate in the object cache level role | | : - : | : | : - | | | level cache ` mAttachedScrap `, ` mChangedScrap ` | ` mChangedScrap ` only participate in the layout, ` mAttachedScrap ` deposit will be reused ` ViewHolder ` | | | secondary cache ` mCachedViews ` | holds up two cache ` ViewHolder ` | | 3 | cache ` mViewCacheExtension ` custom realize | | to developers | | 4 cache ` mRecyclerPool ` | understandable ` RecyclerPool ` is ` (int, ArrayList < ViewHolder >) of ` ` SparseArray `, key is ` viewType `, Each ` viewType ` can hold at most five ` ViewHolder ` | # # # # # 2.2 get ViewHolder entrance is ` Recycler# getViewForPosition `, there is a position of parameters:Copy the code
public View getViewForPosition(int position) { return getViewForPosition(position, false); }
// The name of the function. It’s trying to get ViewHolder View getViewForPosition(int Position, Boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; }
As you can guess from the name of the function, 'itemView' in 'ViewHolder' is the subview we want to get. How does' ViewHolder 'get it?Copy the code
ViewHolder tryGetViewHolderForPositionByDeadline(int position, Boolean dryRun, long deadlineNs) { //… ViewHolder holder = null; // The first step is to get PreLayout from mChangedScrap, which is not the actual layout. // Otherwise, it is meaningless and does not participate in the actual layout caching process. if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! = null; } // RecyclerView = RecyclerView = RecyclerView = RecyclerView = RecyclerView = RecyclerView It’s going to take those ViewHolder out first and put them in the mAttachedScrap, / / fill out if mAttachedScrap when (holder = = null) {holder = getScrapOrHiddenOrCachedHolderForPosition (position, dryRun); / /… } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); final int type = mAdapter.getItemViewType(offsetPosition); // Find from scrap/cache via stable ids, If exists if (mAdapter.hasstableids ()) {// StableId can be used as a ViewHolder to uniquely identify holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); / /… } if (holder == null && mViewCacheExtension! = null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); / /… } if (holder == null) {// Step 5: RecycledViewPool // Get a RecycledViewPool // A maximum of five ViewHolder of each ViewType can be stored = getRecycledViewPool().getRecycledView(type); / /… } if (holder == null) {// Will need to recreate holder = mAdapter. CreateViewHolder (RecyclerView. This type); / /… } } Boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // … } else if (! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { // … / / without binding rebind bound = tryBindViewHolderByDeadline (holder, offsetPosition, position, deadlineNs); } / /… return holder; }
We can use the recycle_cache_extension to get the ViewHolder. We can use the recycle_cache_extension to get the ViewHolder. We can use the recycle_cache_extension to get the ViewHolder. Then 'Recycler' has to call 'Adapter#createViewHolder' to create again, which is our old friend, and it's still in 'Adapter'. Let's have a look at 'Adapter#createViewHolder' :Copy the code
public final VH createViewHolder(ViewGroup parent, int viewType) { // … final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; // … return holder; }
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
The actual ViewHolder is created by the 'Adapter#onCreateViewHolder' method, which is the abstract method we must implement when inheriting the Adapter. We're going to do the binding of the child View to the data, and before we go back to the View, the binding of the View is definitely done, so let's see where the binding of the View happens. We return a method on ` Recycler# tryGetViewHolderForPositionByDeadline `, you can see in the fourth row from bottom, the execution ` Recycler# tryBindViewHolderByDeadline ` method:Copy the code
private Boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, int position, long deadlineNs) { // … MAdapter #bindViewHolder madapter.bindviewholder (holder, offsetPosition); // the key method is to call Adapter#bindViewHolder madapter.bindviewholder (holder, offsetPosition); / /… }
public void onBindViewHolder(VH holder, int position, List payloads) { onBindViewHolder(holder, position); }
public abstract void onBindViewHolder(VH holder, int position);
Successfully see the 'Adapter#onBindViewHolder' method that we must implement. Once this is done, the child 'View' is handed over to the 'LayoutManager'. ##### 2.2 Recycling ViewHolder 'ViewHolder' can be recycled in a variety of scenarios, such as sliding, data deletion, and so on. We are here to slide as a recycling scene, and only analyze the finger touch when sliding, sliding entry in the 'RecyclerView#onTouchEvent' :Copy the code
public Boolean onTouchEvent(MotionEvent e) { // … switch (action) { // … case MotionEvent.ACTION_MOVE: { // … if (mScrollState == SCROLL_STATE_DRAGGING) { mLastTouchX = x – mScrollOffset[0]; mLastTouchY = y – mScrollOffset[1]; // This example demonstrates that the current sliding state is set to SCROLL_STATE_DRAGGING (scrollByInternal(canScrollHorizontally? dx : 0, canScrollVertically ? dy : 0, vtev)) { getParent().requestDisallowInterceptTouchEvent(true); } / /… } } break; / /… } / /… return true; }
After code simplification, we only need to focus on 'RecyclerView#scrollByInternal' :Copy the code
Boolean scrollByInternal(int x, int y, MotionEvent ev) { // … if (mAdapter ! = null) { // … // LayoutManager handles both landscape and portrait if (x! = 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); unconsumedX = x – consumedX; } if (y ! = 0) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); unconsumedY = y – consumedY; } / /… } / /… return consumedX ! = 0 || consumedY ! = 0; }
In section 2.2, we only talk about the fetching of views in this process. In fact, in this process, we also talk about the recycling of views. During the collection process, 'LayoutManager' does the following: 1. Find the view to recycle. 2. Notify the parent layout (RecyclerView) to remove child views. 3. Notify 'Recycler' to manage the Recycler. We focus on ** 'recyer' to manage **, the entry of recycling is' recycleView ':Copy the code
public void recycleView(View view) { // … ViewHolder holder = getChildViewHolderint(view); // … recycleViewHolderInternal(holder); }
Void recycleViewHolderInternal (ViewHolder holder) {/ / tests / /… Boolean cached = false; Boolean recycled = false; / /… if (forceRecycle || holder.isRecyclable()) { if (mViewCacheMax > 0 && ! holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | Viewholder.flag_adapter_position_unknown)) {// mViewCacheMax default maximum is 2 int cachedViewSize = McAchedviews.size (); If (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {// Remove the ViewHolder when the number of caches is greater than 2; cachedViewSize–; } / /… mCachedViews.add(targetCacheIndex, holder); cached = true; } if (! cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } } else { // … } / / remove mViewInfoStore ViewInfoStore removeViewHolder (holder); }
From the above ` Recycler# recycleViewHolderInternal ` method, you can see that ` ViewHolder ` will be preferred to join ` mCachedViews `, when ` mCachedViews ` number greater than 2, Recycler#recycleCachedViewAt Recycler#recycleCachedViewAtCopy the code
void recycleCachedViewAt(int cachedViewIndex) { // … ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); / / add into the buffer pool RecyclerPool addViewHolderToRecycledViewPool (viewHolder, true); // Remove mCachedViews from mCachedViews. Remove (cachedViewIndex); }
Since 'cachedViewIndex' is 2, if 'ViewHolder' in 'mCachedViews' has a number of 2, it will be added to' mCachedViews' first, Then remove the advanced 'ViewHolder' from 'mCachedViews' and add it to the cache pool. I have selected some common scenes and put together the following images:! Common use Recycler caching scenario] [(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/067fed4de92249eeb31c38fcbd7afcbf~tplv-k3u1fbpfcp-zo Om-1. Image) 1. 'mChangedScrap' does not actually participate in the real caching process, and its addition and removal of 'ViewHolder' appear in the 'PreLayout' process of 'RecyclerView#dispatchLayoutStep1' method. 2. For the 'ViewHolder' that has been displayed and will continue to be displayed in 'RecyclerView', the 'ViewHolder' and its child 'View' will be removed from 'RecyclerView' and added to 'mAttachedScrap' during the redrawing process. And from 'MathedScrap' during the subsequent filling of child 'View'. 3. 'mCachedViews' can cache a maximum of two' ViewHolder '. If the number is greater than the maximum, the advanced 'ViewHolder' will be taken out and added to the 'RecycledViewPool'. 4. 'RecycledViewPool' provides a maximum of 5 caches for each 'ViewHolder' of 'viewType'. After having 'Recycler' :! [android interface homepage. JPG] (HTTP: / / https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f105d85e05324f6c89fa71686861c714~tplv-k3u1fbpfcp-z Oom-1.image) gray is small T students mobile phone screen, view chat records, 'RecyclerView' will not every time to create a new 'ViewHolder', nor will all the 'ViewHolder' are built at once, reduce the loss of memory and time, so, In the process of reading the source code, we found that 'RecyclerView' used a lot of design modes. If you look at the name of the 'Adapter' class, you can see that it uses the ** Adapter mode ** because it involves converting a dataset into the subviews needed by the 'RecyclerView'. RecyclerView#setAdapter (); RecyclerView#setAdapter (); Then register the listener for the new Adapter, wait until the data changes, notify the observer, the observer can happily delete or add subviews in the 'RecyclerView'. Then, look at the class 'LayoutManager', 'RecyclerView' will give the 'View' layout this task to the abstract class 'LayoutManager', according to different needs, such as linear layout can be implemented with 'LinearLayoutManager', Grid layout can be used with 'GridLayoutManager'. To deal with the same layout problem, 'RecyclerView' uses the ** strategy mode ** to provide a different solution, as does' ItemAnimator '. If you are interested, you can check the corresponding source code. In this paper, in addition to the deep study of 'Recycler', the other point to the end, roughly the following conclusions:! [summary] (https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d90cbef401124553bd998763fa241d38~tplv-k3u1fbpfcp-zoom-1.image) In a future blog post, I'll learn the rest of the RecyclerView with you. This is probably one of the most uncomfortable blog I wrote, one is' RecyclerView 'source code is very long, looking a little tired; Second, source code analysis of the blog really do not know how to write, is still in continuous exploration. Ha ha ~, finally finished the first article, my level is limited, inevitable mistakes, welcome to point out yo ~ if you are interested in this series of articles: > second article: [the cobwebs of the RecyclerView - LayoutManager] (https://juejin.cn/post/6844903924256735239) refer to the article: > [the RecyclerView source code parsing] (https://juejin.cn/post/6844903459011969037#comment) [RecyclerView cache principle, Picture is truth] (https://juejin.cn/post/6844903661726859271) [the RecyclerView caching mechanism (zha reuse?)] (https://juejin.cn/post/6844903778303344647) [the RecyclerView animation source analyses] (https://juejin.cn/post/6844903743352209421) [the Understanding RecyclerView Components. Parts - 2] (https://android.jlelse.eu/understanding-recyclerview-components-part-2-1fd43001a98f)Copy the code