1.前言
2021年4月7日Android团队正式发布了RecyclerView 1.2.0版本。相对于1.1.0版本,它有两个主要的变化:
- 增加了ConcatAdapter:这个Adapter方便地让我们在一个RecyclerView中连接多个Adapters。
- 支持延迟恢复状态:RecyclerView现在支持当内容加载出来后恢复状态。
本文将结合ConcatAdapter的简单使用,由浅入深地讲解ConcatAdapter的高级使用。
2.简单使用
实现上面是文本列表,下面是按钮列表的效果,如图:
2.1 不使用ConcatAdapter实现
在RecyclerView 1.2.0之前,我们可以通过Adapte的getItemViewType方法,设置文本和按钮两种类型。来完成上述效果。伪代码如下,通过TEXT_TYPE和BUTTON_TYPE两种类型,创建不同的视图。
2.2 使用ConcatAdapter实现
使用ConcatAdapter实现该效果。只需要创建TextAdapter处理文本列表,创建ButtonAdapter处理按钮列表。通过ConcatAdapter将它们串联起来。代码如下:
2.3 优势和劣势
使用ConcatAdapter的优势是Adapter可重用性高,更专注在业务上,不必考虑各种不同ItemType的场景,耦合度低。劣势是,ConcatAdapter不支持不同ItemType交叉出现的场景。
3. 高级进阶
以上就是ConcatAdapter简单使用的全部教程。但是如果你认为ConcatAdapter就这么简单那你就大错特错了。让我们深入源码,玩点更高级的特性吧。
3.1 Config类
我们看到ConcatAdapter有如下构造函数。我们注意到Config类是ConcatAdapter的静态内部类。
public ConcatAdapter(Adapter<? extends ViewHolder>... adapters) { this(Config.DEFAULT, adapters); } public ConcatAdapter(Config config, Adapter<? extends ViewHolder>... adapters) { this(config, Arrays.asList(adapters)); }
Config构造函数如下:
Config(boolean isolateViewTypes, StableIdMode stableIdMode) { this.isolateViewTypes = isolateViewTypes; this.stableIdMode = stableIdMode; }
public static final Config DEFAULT = new Config(true, NO_STABLE_IDS);
我们注意到默认的Config,isolateViewTypes值为true。
3.2 isolateViewTypes含义
要讲清楚isolateViewTypes的含义,那么必须先明白viewType与缓存的关系。我们都知道RecyclerViewPool中是根据viewType缓存ViewHolder的。如果viewType相同,那么它对应的缓存池相同。
RecyclerViewPool缓存示意图如下。每个不同的viewType都有一个属于它自己的缓存。
isolateViewTypes为true。表示ConcatAdapter中的子Adapter的viewType,会被ConcatAdapter隔离开。即使两个子Adapter的中元素的viewType相同,ConcatAdapter会将它们分隔成不同的viewType。从缓存的角度看,即使两个相同的Adapter,它们也无法共用一个缓存池。
isolateViewTypes为false。表示如果viewType相同,那么它们将共用一个缓存池。
3.1 不共用缓存
假设有ConcatAdapter,连接了RedAdapter、OrangeAdapter、BlueAdapter、RedAdapter。使用默认Config。isolateViewTypes为true,我们看到RedAdapter的ViewType默认返回1。但是从ConcatAdapter的角度。两个RedAdapter的viewType分别为0和3。
RedAdapter redAdapter1 = xxx; OrangeAdapter orangerAdapter = xxx; BlueAdapter blueAdapter = xxx; RedAdapter redAdapter2 = xxx; ConcatAdapter concatenated = new ConcatAdapter(redAdapter1, orangerAdapter,blueAdapter,redAdapter2); recyclerView.setAdapter(concatenated);
3.2 共用缓存
RedAdapter redAdapter1 = xxx; OrangeAdapter orangerAdapter = xxx; BlueAdapter blueAdapter = xxx; RedAdapter redAdapter2 = xxx; //isolateViewTypes为false ConcatAdapter.Config config = ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build() ConcatAdapter concatenated = new ConcatAdapter(config, redAdapter1, orangerAdapter,blueAdapter,redAdapter2); recyclerView.setAdapter(concatenated);
ConcatAdapter的itemType 和子Adapter的itemType一致。
4. 爬坑
4.1 坑一
子Adapter的getItemViewType返回默认值。ConcatAdapter isolateViewType设置为false。
TextAdapter和ButtonAdapter和上文一样。
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() ) } }
4.2 坑二
ConcatAdapter连接多个TextAdapter。isolateViewType设置为true。发现滚动到第二个TextAdapter位置时,又创建了新的TextView。这种情况,相同viewType需要共用缓存。将isolateViewType设置为false。
recyclerView.adapter = ConcatAdapter( config, TextAdapter(), ButtonAdapter(), TextAdapter() )
5. 原理
- ConcatAdapter.getItemViewType()
//ConcatAdapter.java @Override public int getItemViewType(int position) { return mController.getItemViewType(position); }
ConcatAdapterController.getItemViewType(int globalPosition)
//ConcatAdapterController.java public int getItemViewType(int globalPosition) { //根据globalPosition找到对应的子Adapter的Wrapper WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition); releaseWrapperAndLocalPosition(wrapperAndPos); return itemViewType; }
- NestedAdapterWrapper.getItemViewType(int localPosition)
int getItemViewType(int localPosition) { return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition)); }
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; }
IsolatedViewTypeStorage$WrapperViewTypeLookup。从代码可以看出在不共享缓存池的情况下。「子Adapter的viewType会从0递增对应」
int mNextViewType = 0; int obtainViewType(NestedAdapterWrapper wrapper) { int nextId = mNextViewType++; mGlobalTypeToWrapper.put(nextId, wrapper); return nextId; }
- isolateViewTypes为true的情况下。会使用SharedIdRangeViewTypeStorage$WrapperViewTypeLookup。我们看到 localType和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; }