1. Introduction
On April 7, 2021, the Android team officially released RecyclerView 1.2.0. There are two major changes from version 1.1.0:
- Added ConcatAdapter: This Adapter makes it easy to connect multiple Adapters in a RecyclerView.
- Support for delayed state restoration: RecyclerView now supports state restoration when content is loaded.
This article will explain the advanced use of ConcatAdapter from simple to deep.
2. Easy to use
Create a text list above and a button list below, as shown below:
2.1 Do not use ConcatAdapter implementation
Before RecyclerView 1.2.0, we can use the Adapte getItemViewType method, set the text and button two types. To achieve the above effect. The pseudocode below creates different views using the TEXT_TYPE and BUTTON_TYPE types.
2.2 Using ConcatAdapter
Use ConcatAdapter to achieve this effect. Just create a TextAdapter to handle the text list and a ButtonAdapter to handle the button list. They are connected in series through ConcatAdapter. The code is as follows:
2.3 Advantages and Disadvantages
The advantage of using ConcatAdapter is that the Adapter is more reusable, more focused on business, and does not have to consider scenarios of different Itemtypes, and the coupling degree is low. The disadvantage is that ConcatAdapter does not support scenarios where different itemtypes cross.
3. Advanced advanced
That’s it for the simple use of ConcatAdapter. But if you think ConcatAdapter is that simple, you’re wrong. Let’s dig into the source code and play with some more advanced features.
3.1 the Config class
We see that ConcatAdapter has the following constructor. Notice that the Config class is the static inner class of ConcatAdapter.
public ConcatAdapter(Adapter<? extends ViewHolder>... adapters) {
this(Config.DEFAULT, adapters);
}
public ConcatAdapter(Config config, Adapter<? extends ViewHolder>... adapters) {
this(config, Arrays.asList(adapters));
}
Copy the code
The Config constructor looks like this:
Config(boolean isolateViewTypes, StableIdMode stableIdMode) {
this.isolateViewTypes = isolateViewTypes;
this.stableIdMode = stableIdMode;
}
Copy the code
public static final Config DEFAULT = new Config(true, NO_STABLE_IDS);
Copy the code
We note that the default Config, isolateViewTypes values are true.
3.2 isolateViewTypes meaning
To understand what isolateViewTypes mean, you must first understand the relationship of viewTypes to caching. We all know that in RecyclerViewPool ViewHolder is cached by viewType. If the viewType is the same, then it corresponds to the same cache pool.
RecyclerViewPool cache is as follows. Each different viewType has its own cache.
IsolateViewTypes to true. A viewType that represents a child Adapter in a ConcatAdapter and is isolated by a ConcatAdapter. Even if the elements in two child Adapters have the same viewType, ConcatAdapter separates them into different viewtypes. From a cache perspective, even two identical Adapters cannot share a cache pool.
IsolateViewTypes to false. Indicates that if the viewtypes are the same, they will share a cache pool.
3.1 Do not Share cache
Assume that there is a ConcatAdapter that connects RedAdapter, OrangeAdapter, BlueAdapter, and RedAdapter. Use the default Config. IsolateViewTypes is true and we see that RedAdapter ViewType returns 1 by default. But from the ConcatAdapter point of view. The viewtypes of the two RedAdapters are 0 and 3 respectively.
RedAdapter redAdapter1 = xxx;
OrangeAdapter orangerAdapter = xxx;
BlueAdapter blueAdapter = xxx;
RedAdapter redAdapter2 = xxx;
ConcatAdapter concatenated = new ConcatAdapter(redAdapter1, orangerAdapter,blueAdapter,redAdapter2);
recyclerView.setAdapter(concatenated);
Copy the code
3.2 Shared Cache
RedAdapter redAdapter1 = xxx; OrangeAdapter orangerAdapter = xxx; BlueAdapter blueAdapter = xxx; RedAdapter redAdapter2 = xxx; / / isolateViewTypes to false ConcatAdapter. Config Config = ConcatAdapter. Config. The Builder () setIsolateViewTypes (false). The build () ConcatAdapter concatenated = new ConcatAdapter(config, redAdapter1, orangerAdapter,blueAdapter,redAdapter2); recyclerView.setAdapter(concatenated);Copy the code
The itemType of a ConcatAdapter is the same as the itemType of a subadapter.
4. Climb the pit
4.1 a pit
GetItemViewType of the child Adapter returns the default value. ConcatAdapter isolateViewType set to false.
TextAdapter and ButtonAdapter are the same as above.
class ConcatAdapterDemoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_concat_adapter_demo)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.layoutManager = LinearLayoutManager(this)
val config = ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build()
recyclerView.adapter = ConcatAdapter(
config,
TextAdapter(),
ButtonAdapter()
)
}
}
Copy the code
4.2 two pit
ConcatAdapter Connects multiple TextAdapters. Set isolateViewType to true. When you scroll to the second TextAdapter location, you create a new TextView. In this case, the same viewType needs to share the cache. Set isolateViewType to false.
recyclerView.adapter = ConcatAdapter(
config,
TextAdapter(),
ButtonAdapter(),
TextAdapter()
)
Copy the code
Principle 5.
- ConcatAdapter.getItemViewType()
//ConcatAdapter.java
@Override
public int getItemViewType(int position) {
return mController.getItemViewType(position);
}
Copy the code
- ConcatAdapterController.getItemViewType(int globalPosition)
/ / ConcatAdapterController. Java public int getItemViewType (int globalPosition) {/ / based on Wrapper son globalPosition find corresponding Adapter WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition); releaseWrapperAndLocalPosition(wrapperAndPos); return itemViewType; }Copy the code
- NestedAdapterWrapper.getItemViewType(int localPosition)
int getItemViewType(int localPosition) {
return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
}
Copy the code
- IsolatedViewTypeStorage$WrapperViewTypeLookup.localToGlobal()
@Override
public int localToGlobal(int localType) {
int index = mLocalToGlobalMapping.indexOfKey(localType);
if (index > -1) {
return mLocalToGlobalMapping.valueAt(index);
}
// get a new key.
int globalType = obtainViewType(mWrapper);
mLocalToGlobalMapping.put(localType, globalType);
mGlobalToLocalMapping.put(globalType, localType);
return globalType;
}
Copy the code
- IsolatedViewTypeStorage $WrapperViewTypeLookup. You can see from the code that in the case of not sharing the cache pool. The viewType of the child Adapter is incremented from 0
int mNextViewType = 0;
int obtainViewType(NestedAdapterWrapper wrapper) {
int nextId = mNextViewType++;
mGlobalTypeToWrapper.put(nextId, wrapper);
return nextId;
}
Copy the code
- IsolateViewTypes is true. Will use SharedIdRangeViewTypeStorage $WrapperViewTypeLookup. We see that localType is equal to globalType.
@Override public int localToGlobal(int localType) { // register it first List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.get( localType); if (wrappers == null) { wrappers = new ArrayList<>(); mGlobalTypeToWrapper.put(localType, wrappers); } if (! wrappers.contains(mWrapper)) { wrappers.add(mWrapper); } return localType; } @Override public int globalToLocal(int globalType) { return globalType; }Copy the code
6. The final
It was a little rushed. There are some points that are missing. Leave a message if you have any questions. Welcome to follow the wechat public account “Bytecode”. Get more dry articles.