Today we will talk about recyclerView 1.2.0 version of the new MergeAdapter function

Add dependencies as follows:

// We are still in beta now, please change to official implementation later'androidx. Recyclerview: recyclerview: 1.2.0 - alpha03'
Copy the code

MergeAdapter

Since RecyclerView can only bind one Adapter, if different ViewHolder needs to be implemented, getItemViewType can only be rewritten to achieve this, which will cause a little high coupling and not easy to expand

MergeAdapter as its name implies is to merge multiple adapters into one adapter, set to RecyclerView, so as to realize a adapter responsible for the rendering of a layout (or a business logic data rendering), so as to achieve business code decoupling

In the actual business process, there is often a list of the same data, nested in different pages; This is a good time to use MergeAdapter;

For example, different page headers display different layouts, but the following data list is the same (HeaderAdapter) recommendation/introduction/advertising list + (Comment list/video list), which can well realize the decoupling of different business logic code. Otherwise you must couple the HeaderAdapter code into NormalAdapter

Another feature that we use a lot is to pull up and load more features; It is ideal for MergeAdapter implementation

Let’s take a quick look at the use of MergeAdapter

class MergeAdapterActivity : AppCompatActivity() {

    private var mergeAdapter: MergeAdapter = MergeAdapter()
    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter) recycler_view.layoutManager = LinearLayoutManager(this) val mergeAdapter = Add (0) normalAdapter.data.add(0) footadapter.data.add (0) // Mergeadapter.addadapter (headerAdapter) // Render normalAdapter data mergeAdapter.addAdapter(normalAdapter) Mergeadapter.addadapter (footAdapter) recycler_view.adapter = mergeAdapter} // Add data to normalAdapter  fun addNormalData(view: View) { normalAdapter.data.add(normalAdapter.data.size) normalAdapter.notifyItemInserted(normalAdapter.data.size - 1) } Funremovefootdata (view: View) { normalAdapter.data.removeAt(0) normalAdapter.notifyItemRemoved(0) } }Copy the code

The renderings are as follows

It’s very simple to use, you just call addAdapter to addAdapter, how do you use the old Adapter to add and remove the refresh data or how do you use it, basically the same thing

MergeAdapter also has the removeAdapter method to remove the Adapter, so that all attempts to render the Adapter are removed

If you look at the MergeAdapter source code, you will find that the core logic is implemented by MergeAdapterController

MergeAdapter.Config

MergeAdapter Supports setting mergeAdapter.config in the constructor

/**
 * Creates a MergeAdapter with the given config and the given adapters in the given order.
 *
 * @param config   The configuration for this MergeAdapter
 * @param adapters The list of adapters to add
 * @see Config.Builder
 */
@SafeVarargs
public MergeAdapter(
        @NonNull Config config,
        @NonNull Adapter<? extends ViewHolder>... adapters) {
    this(config, Arrays.asList(adapters));
}
Copy the code

Mergeadapter. Config can now configure isolateViewTypes and stableIdMode

isolateViewTypes
/**
 * If {@code false}, {@link MergeAdapter} assumes all assigned adapters share a global
 * view type pool such that they use the same view types to refer to the same
 * ....
 */
public final boolean isolateViewTypes;
Copy the code

The isolateViewTypes are used to set whether each Adapter has its own view Type pool. False indicates that each Adapter shares a view Type pool. True indicates that each Adapter uses its own view Type pool. The default value is true

Let’s start with some simple code

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(r.layout.activity_merge_adapter) // Add a data.headerAdapter.data.add (0) normalAdapter.data.add(0) Footadapter.data.add (0) recycler_view1.layoutManager = LinearLayoutManager(this) // Set IsolateViewTypes=false
    val mergeAdapter1 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(false).build()) mergeAdapter1.addAdapter(headerAdapter) mergeAdapter1.addAdapter(normalAdapter) mergeAdapter1.addAdapter(footAdapter) recycler_view1.adapter = mergeAdapter1 recycler_view2.layoutManager = LinearLayoutManager(this) // Set IsolateViewTypes=true
    val mergeAdapter2 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(true).build())
    mergeAdapter2.addAdapter(headerAdapter)
    mergeAdapter2.addAdapter(normalAdapter)
    mergeAdapter2.addAdapter(footAdapter)
    recycler_view2.adapter = mergeAdapter2
}
Copy the code

The renderings are as follows:

NormalAdapter and footAdapter do not create their own ViewHolder when isolateViewTypes=false and itemViewType is the same for each adapter (default is 0). It’s the ViewHolder of the created headerAdapter (HeaderViewHolder background is red, NormalViewHolder background is blue, FootViewHolder background is black)

Key source code is as follows

class MergeAdapterController implements NestedAdapterWrapper.Callback {
	
	...
	
	public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
	    NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);
	    returnwrapper.onCreateViewHolder(parent, globalViewType); } @NonNull @Override public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) { // List<NestedAdapterWrapper> nestedAdapterWrappers = mGlobalTypeToWrapper. Get ( globalViewType);if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) {
	        throw new IllegalArgumentException("Cannot find the wrapper for global view"
	                + " type " + globalViewType);
	    }
	    // just returnThe first one since they are shared The ViewHolder is then created by calling the onCreateViewHolder method of the first AdapterreturnnestedAdapterWrappers.get(0); }}Copy the code

Therefore, when isolateViewTypes=false, it is relatively important to clarify which Adapter ViewHolder is being created to avoid confusing problems

When isolateViewTypes=true, each Adapter creates its own ViewHolder, which needs no further explanation

stableIdMode

StableIdMode is an enumeration with three values: NO_STABLE_IDS, ISOLATED_STABLE_IDS, SHARED_STABLE_IDS; It is used to set the return value of Adapter’s hasStableIds

class MergeAdapterController implements NestedAdapterWrapper.Callback {

	...
	
	public boolean hasStableIds() {
	    return mStableIdMode != NO_STABLE_IDS;
	}
}

Copy the code

For this parameter, use the default value NO_STABLE_IDS

MergeAdapter limit

  1. Can’t mistake complex dynamic display of different typesViewHolder.MergeAdapterIs based on eachAdapterDisplaying data in the order of; Or you need to cooperateItemViewTypeImplement complex business scenarios
  2. Due to theLayoutManagerIs set inRecyclerViewOn, so eachAdapterThe layout is the sameLayoutManager)

That’s all I can think of for now.

GetItemViewType Possible pits

This refers to the getItemViewType method of the ViewHolder, not the getItemViewType method of the Adapter

Here said the pit for androidx. Recyclerview: recyclerview: 1.2.0 – alpha03 closed beta version, if later versions Please ignore

Why would you say that, because it might not return the value that you expected

When setting isolateViewTypes=true, we code and log

Class MergeAdapterActivity:AppCompatActivity() {

    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter)
			
        headerAdapter.data.addAll(listOf(0, 1, 2))
        normalAdapter.data.addAll(listOf(0, 1, 2))
        footAdapter.data.addAll(listOf(0, 1, 2))
        ...

        recycler_view2.layoutManager = LinearLayoutManager(this)
        val mergeAdapter2 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(true).build()) mergeAdapter2.addAdapter(headerAdapter) mergeAdapter2.addAdapter(normalAdapter) Mergeadapter2.addadapter (footAdapter) recycler_view2.adapter = mergeAdapter2}} // HeaderAdapter source code class HeaderAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { var data: MutableList<Any? > = mutableListOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { Log.i("Adapter"."create HeaderViewHolder, viewType:$viewType")
        return HeaderViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_header, parent, falseOverride fun getItemViewType(position: Int): Int {return if (position == 0) 100 else 200
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter"."HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "header data: $position, itemViewType: ${holder.itemViewType}"}... } // NormalAdapter source code class NormalAdapter: recyclerview. Adapter< recyclerview. ViewHolder>() {var data: MutableList<Any? > = mutableListOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { Log.i("Adapter"."create NormalViewHolder, viewType:$viewType")
        return NormalViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_normal, parent, falseOverride fun getItemViewType(position: Int): Int {return if (position == 0) 300 else 400
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter"."NormalViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "normal data: $position, itemViewType: ${holder.itemViewType}"}... } // FootAdapter source code class FootAdapter: recyclerview. Adapter< recyclerview. ViewHolder>() {var data: MutableList<Any? > = mutableListOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { Log.i("Adapter"."create FootViewHolder, viewType:$viewType")
        return FootViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_foot, parent, falseOverride fun getItemViewType(position: Int): Int {return if (position == 0) 500 else 600
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter"."FootViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "foot data: $position, itemViewType: ${holder.itemViewType}"}... }Copy the code

The following logs are displayed:

Adapter: create HeaderViewHolder, viewType:100
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 0, adapter getItemViewType: 100
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 1, adapter getItemViewType: 200
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 1, adapter getItemViewType: 200
Adapter: create NormalViewHolder, viewType:300
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 2, adapter getItemViewType: 300
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 3, adapter getItemViewType: 400
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 3, adapter getItemViewType: 400
Adapter: create FootViewHolder, viewType:500
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 4, adapter getItemViewType: 500
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 5, adapter getItemViewType: 600
Copy the code

The getItemViewType method of viewHolder does not return a custom value, but a value that increments from zero.

After analyzing the source code, let’s look at the effect of isolateViewTypes=false

class MergeAdapterActivity : AppCompatActivity() {

    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter)
        headerAdapter.data.addAll(listOf(0, 1, 2))
        normalAdapter.data.addAll(listOf(0, 1, 2))
        footAdapter.data.addAll(listOf(0, 1, 2))

        recycler_view1.layoutManager = LinearLayoutManager(this)
        val mergeAdapter1 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(false).build()) mergeAdapter1.addAdapter(headerAdapter) mergeAdapter1.addAdapter(normalAdapter) Mergeadapter1.addadapter (footAdapter) recycler_view1.adapter = mergeAdapter1}}Copy the code

The following logs are displayed:

Adapter: create HeaderViewHolder, viewType:100
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 100, adapter getItemViewType: 100
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 200, adapter getItemViewType: 200
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 200, adapter getItemViewType: 200
Adapter: create NormalViewHolder, viewType:300
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 300, adapter getItemViewType: 300
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 400, adapter getItemViewType: 400
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 400, adapter getItemViewType: 400
Adapter: create FootViewHolder, viewType:500
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 500, adapter getItemViewType: 500
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 600, adapter getItemViewType: 600
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 600, adapter getItemViewType: 600
Copy the code

The getItemViewType method of the viewHolder returns the custom value

Personally, I think it may be a Google bug, because after all, it is still in private beta. Let’s see if there is still such a problem after the official release

Let’s look at the source code, why?

/ / this is RecyclerView ViewHolder creation method, removed most of the code @ Nullable ViewHolder tryGetViewHolderForPositionByDeadline (int the position, boolean dryRun, long deadlineNs) { ...if(holder == null) { ... // Get ViewType, where mAdapter is MergeAdapter final inttype= mAdapter.getItemViewType(offsetPosition); .if(holder == null) { ... / / to createViewHolder holder = mAdapter. CreateViewHolder (RecyclerView. This,type); . }}...returnholder; } @nonNULL public final VH createViewHolder(@nonnull ViewGroup parent, int viewType) { try { ... // Assign viewType directly to viewholder mItemViewType holder. MItemViewType = viewType;returnholder; } finally { TraceCompat.endSection(); }} // This is the getItemViewType method of MergeAdapter, @override public int getItemViewType(int position) {Override public int getItemViewType(int position) {returnmController.getItemViewType(position); } // MergeAdapterController getItemViewType public int getItemViewType(int globalPosition) {WrapperAndLocalPosition  wrapperAndPos = findWrapperAndLocalPosition(globalPosition); // mWrapper is the NestedAdapterWrapper class int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition); releaseWrapperAndLocalPosition(wrapperAndPos);returnitemViewType; } // the getItemViewType method of NestedAdapterWrapper // mViewTypeLookup is the implementation class of ViewTypeLookuplocalPosition) {
    return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
}
Copy the code

Let’s look at the definition of ViewTypeLookup and its two implementation classes, which correspond to IsolatedViewType= True and IsolatedViewType= False

Interface ViewTypeLookup {// Defines the conversion from each adapter's own viewType to global viewType intlocalToGlobal(int localType); Int globalToLocal(int globalType); void dispose(); }Copy the code

First look at the implementation class IsolatedViewType= True

class IsolatedViewTypeStorage implements ViewTypeStorage { SparseArray<NestedAdapterWrapper> mGlobalTypeToWrapper = new SparseArray<>(); int mNextViewType = 0; Int obtainViewType(NestedAdapterWrapper) {int nextId = mNextViewType++; mGlobalTypeToWrapper.put(nextId, wrapper);return nextId;
    }

    class WrapperViewTypeLookup implements ViewTypeLookup {
        private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1);
        private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1);
        final NestedAdapterWrapper mWrapper;

        WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
            mWrapper = wrapper;
        }

        @Override
        public int localToGlobal(int localType) {
            int index = mLocalToGlobalMapping.indexOfKey(localType);
            if (index > -1) {
                returnmLocalToGlobalMapping.valueAt(index); Int globalType = obtainViewType(mWrapper); / / cached mLocalToGlobalMapping. Put (localType, globalType);
            mGlobalToLocalMapping.put(globalType, localType);
            returnglobalType; } // Directly from the mGlobalToLocalMapping cachelocalThe Type,returnOut @ Override public int globalToLocal (int globalType) {int index = mGlobalToLocalMapping. IndexOfKey (globalType);if (index < 0) {
                throw new IllegalStateException("requested global type " + globalType + " does"
                        + " not belong to the adapter:" + mWrapper.adapter);
            }
            returnmGlobalToLocalMapping.valueAt(index); }... }}Copy the code

You can see that globalType is incremented from 0 when IsolatedViewType=true

Let’s look at the subclass implementation when IsolatedViewType=false

class SharedIdRangeViewTypeStorage implements ViewTypeStorage { ... class WrapperViewTypeLookup implements ViewTypeLookup { final NestedAdapterWrapper mWrapper; WrapperViewTypeLookup(NestedAdapterWrapper wrapper) { mWrapper = wrapper; } // Return directlylocalType as globalType@override public intlocalToGlobal(int localType) {
            ...
            return localType;
        }

        @Override
        public int globalToLocal(int globalType) {
            returnglobalType; }... }}Copy the code

Let’s go back to the getItemViewType method of MergeAdapterController

public int getItemViewType(int globalPosition) { WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); GlobalType is returned when 'IsolatedViewType='true// When 'IsolatedViewType=false`, here will return the value of adapter own custom int itemViewType = wrapperAndPos. MWrapper. GetItemViewType (wrapperAndPos. MLocalPosition); releaseWrapperAndLocalPosition(wrapperAndPos); // This results in the ViewHolder value being incorrectreturn itemViewType;
}
Copy the code

So why is the value of the viewType parameter of the onCreateViewHolder method okay? That’s because MergeAdapterController converts the globalType to the localType of each Adapter in onCreateViewHolder (see the source code below)

Public ViewHolder onCreateViewHolder(ViewGroup parent) int globalViewType) { NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);returnwrapper.onCreateViewHolder(parent, globalViewType); } // NestedAdapterWrapper onCreateViewHolder onCreateViewHolder(ViewGroup parent, Int globalViewType) {// Convert globalViewType tolocalType
    int localType = mViewTypeLookup.globalToLocal(globalViewType);
    return adapter.onCreateViewHolder(parent, localType);
}
Copy the code

The source code for this problem has been analyzed to this point. Personally, I think this problem may be a bug or the ViewHolder should provide a new method to retrieve the value of the viewType

Note: this question is for androidx. Recyclerview: recyclerview: 1.2.0 – alpha03 closed beta version of the source code analysis, may be in later versions, if you have any change or repair the later versions of the source code to this problem Please ignore the source analysis

So if you use MergeAdapter in the process must pay attention to this problem, otherwise will crash or inexplicable problems yo

Let’s take a look at some of the methods we use to get position

getAdapterPosition vs getLayoutPosition

GetAdapterPosition returns the position of the ViewHolder bound data in the Adapter

GetLayoutPosition returns the most recent computed position of the ViewHolder layout after rendering, consistent with the position seen by the user

GetLayoutPosition and getAdapterPosition are usually the same; Only when the Adapter data has changed and the Layout has not been drawn in time can it be different

While this simple explanation may seem a bit confusing, let’s try an experiment

Try the notifyDataSetChanged pair first

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder.itemView.tv_text.text = "data: $position"
    holder.itemView.setOnClickListener {
        Log.i("Adapter"."click viewHolder, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}"Thread(Runnable {Thread(Runnable {Thread(Runnable {Thread(Runnable {Thread));while (true) {
                Log.i("Adapter"."data: $position, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}")
                Thread.sleep(5L)
            }
        }).start()
    }
    
    Handler().postDelayed({
        Log.i("Adapter"."==notifyDataSetChanged==")
        notifyDataSetChanged()
    }, 20)
}
Copy the code

Click on the third piece of data; The logs are as follows

Adapter: click viewHolder, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: ==notifyDataSetChanged==
Adapter: data: 2, adapterPosition: -1, layoutPosition: 2
Adapter: data: 2, adapterPosition: -1, layoutPosition: 2
Adapter: data: 2, adapterPosition: -1, layoutPosition: -1
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
...
Copy the code

Because notifyDataSetChanged requires all data to be redrawn, the ViewHolder does not know which position in the Adapter is bound to until the drawing is complete, so -1 (NO_POSITION) is returned.

Instead, layoutPosition caches the previous value for a short time, and adapterPosition and layoutPosition are the same after a Layout is rendered

Let’s try notifyItemRemoved again

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder.itemView.tv_text.text = "data: $position"
    holder.itemView.setOnClickListener {
        Log.i("Adapter"."click viewHolder, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}"Thread(Runnable {Thread(Runnable {Thread(Runnable {Thread(Runnable {Thread));while (true) {
                Log.i("Adapter"."data: $position, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}") thread.sleep (5L)}}).start()} Handler().postdelayed ({data.removeat (0)} log.i ()"Adapter"."==notifyItemRemoved==")
        notifyItemRemoved(0)
    }, 20)
}
Copy the code

Click on the third piece of data; The logs are as follows

Adapter: click viewHolder, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: ==notifyItemRemoved==
Adapter: data: 2, adapterPosition: 1, layoutPosition: 2
Adapter: data: 2, adapterPosition: 1, layoutPosition: 2
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1
Copy the code

Because RecyclerView can automatically calculate the position of the data bound to other ViewHolder in the Adapter based on the position removed by notifyItemRemoved, Therefore, the correct adapterPosition can be obtained immediately, but the Layout is not completed, so layoutPosition is still the previous value, until the Layout is completed, layoutPosition is the latest value

getBindingAdapterPosition vs getAbsoluteAdapterPosition

From recyclerview ` ` 1.2.0 ViewHolder has added new getBindingAdapterPosition and getAbsoluteAdapterPosition two methods;

The getAdapterPosition method introduced above is deprecated. The source code is as follows

/ * * * @return {@link #getBindingAdapterPosition()}
 * @deprecated This method is confusing when adapters nest other adapters.
 * If you are calling this in the context of an Adapter, you probably want to call
 * {@link #getBindingAdapterPosition()} or if you want the position as {@link RecyclerView}
 * sees it, you should call {@link #getAbsoluteAdapterPosition()}.
 */
@Deprecated
public final int getAdapterPosition() {
    return getBindingAdapterPosition();
}
Copy the code

Value is returned directly visible getAdapterPosition getBindingAdapterPosition;

So what is the difference between getBindingAdapterPosition and getAbsoluteAdapterPosition?

Override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... Recycler_view.layoutmanager = LinearLayoutManager(this) val mergeAdapter = mergeAdapter () Mergeadapter.addadapter (HeaderAdapter().apply {data.addall (listOf(1, 2, 3, 4), }) // Add NormalAdapter Mergeadapter.addadapter (NormalAdapter().apply {data.addall (listOf(1, 2, 3, 4), 5)) }) recycler_view.adapter = mergeAdapter } ... // NormalAdapter onBindViewHolder code Override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { holder.itemView.tv_text.text ="data: $position" 
    holder.itemView.setOnClickListener {
        Log.i("Adapter"."click data: $position, bindPosition: ${holder.bindingAdapterPosition}, absolutePosition: ${holder.absoluteAdapterPosition}, layoutPosition: ${holder.layoutPosition}")}}Copy the code

When the first data of NormalAdapter is clicked, the following log is printed

// absolutePosition == 5; Adapter: click data: 0bindPosition: 0, absolutePosition: 5, layoutPosition: 5
Copy the code

Visible getBindingAdapterPosition returns the ViewHolder binding of the location of the data in their own Adapter (not considering the MergeAdapter add other Adapter)

GetAbsoluteAdapterPosition returns the ViewHolder binding data position in the MergeAdapter (according to the position of Adapter, then add the offset in the MergeAdapter)

So in most cases, after should be use getBindingAdapterPosition (instead of getAdapterPosition), if you need to consider the offset, use the getAbsoluteAdapterPosition

As for getLayoutPosition with getAbsoluteAdapterPosition similar, can calculate their adapter offset in the MergeAdapter, returns to its position in the MergeAdapter;

Since getLayoutPosition may be used in older code, it is important to prevent data from crossing boundaries when working with MergeAdapter

Well, that’s all for MergeAdapter!!