赞
踩
去年看项目时发现挺多
RecyclerView
列表 加载不同类型视图采用了ConcatAdapter
,当时想了想印象中没有这个工具啊,后来才发现又是以前欠的债,对于我来说可能是新知识,但相对现在它其实已经有段年纪了,蹭着最近有时间补一下学习心得
先说明一下我的看法,针对ConcatAdapter
到底有没有学习必要?我的答案:我觉得可以学,但是没有太大必要,锦上添花而已
当我搜索 ConcatAdapter
相关内容时,发现挺多内容长得一模一样,基本都是搬得最早的一篇,稍加学习后结合官网+源码方式提出我自己的看法,如有不足,欢迎指正
不知道你有没有和我相同的疑问:在ConcatAdapter
未出现前,我们也可以在同列表加载不同的View样式,那为什么还要在推出ConcatAdapter
呢?
Tip
:今天理论先行吧,免得着急用,却选错了方案
在此之前我们如果需要在RecyclerView
列表中加载多种不同类型的视图时,采用的是同一个Adapter
,主要重写其getItemViewType
方法,然后在onCreateViewHolder
、onBindViewHolder
做逻辑处理,其实这种方式完全可以满足我们的业务场景;
之所以推出ConcatAdapter
只是为了实现高内聚、低耦合的效果,同时其单一职责,方便复用;假设公共Adapter
中业务逻辑复杂,且视图不会穿插显示,那么将不同ViewType
逻辑进行剥离,然后组装成新的Adapter
会更利于维护
从穿插效果来看我们到底选哪种方案,在RecyclerView
中
ItemView
需要穿插展示
,并无固定规律,类似以ABAC形式展示
,最好使用之前的方式,兼容性更强,容错率更高ItemView
展示效果固定
,类似以ABC这样顺序展示
,可以选择采用ConcatAdapter
有的朋友可能想着通过ConcatAdapter
动态去组装itemView
以实现部分穿插效果,其实我个人并不建议,首先ConcatAdapter
内有Config
缓存池的概念,会导致你穿插效果不太理想;同时随着随机性变大,很难去完美的动态组装ConcatAdapter
效果
通过 Google
国内官网来看 ConcatAdapter 是从2021年4月7日发布的RecyclerView1.2.0
版本后才加入的
根据下图官网图例,我们可以看出
ConcatAdapter
作用于RecyclerView
1.2.0及后续版本ConcatAdapter
依旧继承于RecyclerView.Adapter
ConcatAdapter
的简单使用示例仅记录我在写该Demo时遇到的一些小插曲
不知道你一开始有没有发现调用不到 ConcatAdapter
… 其实是因为刚创建的项目内自带的RecyclerView
版本还没到1.2.1
build.app
implementation 'androidx.recyclerview:recyclerview:1.2.1'
LinearLayoutManager
来控制展示方向,可以用构造方法,也可以直接用实例调用内部方法
默认垂直显示
var linearLayoutManager = LinearLayoutManager(this)
可以手动设置方向
var linearLayoutManager = LinearLayoutManager(this)
linearLayoutManager.orientation=LinearLayoutManager.VERTICAL
LinearLayoutManager
构造方法
ConcatAdapter
多种构造方式,针对于 Config
可传可不传,具体看场景
在
ConcatAdapter
的构造方法中可以随处可见Config
,这个应该可以算是其自由特性,如无特殊需求无需设置
通过Config
调用提示可以看出它的主要方法
Config
相关源码并不多,主要就是这俩个成员变量
isolateViewTypes
缓存,根据注释翻译+自我理解,结果如下
ConcatAdapter
在适配器内隔离视图类型,防止它们使用相同的RecyclerView.ViewHolders
;ConcatAdapter
假定所有分配的适配器共享一个全局视图类型池,以便它们使用相同的视图类型来引用相同的RecyclerView.ViewHolders
;将允许嵌套适配器共享RecyclerView
。但这也意味着这些适配器不应该有冲突的视图类型(RecyclerView.Adapter.getItemViewType(int)
),这样两个不同的适配器为不同的RecyclerView.ViewHolders
返回相同的视图类型;简而言之就是表示如果viewType
相同,那么它们将共用一个缓存池Tip
:我把isolateViewTypes
中true场景看做二维数组(兼容性强,每一个item相互独立且唯一),false场景看做一维数组(如果组内采用的viewHodler
不同那么就会报错,反之会报转换错误)
isolateViewTypes
为 false 场景模拟(可正常显示视图叠加效果)
val firstAdapter = FirstAdapter(firstDataList)
val secondAdapter = FirstAdapter(firstDataList)
var config = ConcatAdapter.Config.Builder()
.setIsolateViewTypes(false)
.build()
val concatAdapter = ConcatAdapter(config, firstAdapter, secondAdapter)
stableIdMode:定义ConcatAdapter
是否应该支持稳定id (RecyclerView.Adapter.hasStableIds()
) - 我看别人记录的getItemViewType
坑的问题,或许可以通过这里尝试解决,因为时间未必我就不赘述了
StableIdMode 对应模式,源码解释
这俩种方式在实战场景中各有千秋,一个是固定知道有多少视图样式,另一个是只有某些条件触发才会增删内部Adapter~
通过构造方法传入多Adapter
val concatAdapter = ConcatAdapter(config,firstAdapter, secondAdapter)
通过内部方法传入多Adapter
val concatAdapter = ConcatAdapter()
concatAdapter.addAdapter(firstAdapter)
concatAdapter.addAdapter(secondAdapter)
addAdapter
方法提供了将Adapter
插入到指定位置的重载方法
删除某一视图对应的Adapter
val concatAdapter = ConcatAdapter()
concatAdapter.addAdapter(firstAdapter)
concatAdapter.removeAdapter(firstAdapter)
如果只是为了开发使用的话,直接看调用实践(MainActivity
)即可
因为涉及到列表多样式,所以首先准备一些对应 itemLayout
和 Adapter
RecyclerView
的ItemView
之 - FirstAdapter
package com.example.concatadatper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class FirstAdapter(private val dataList: MutableList<String>) : RecyclerView.Adapter<FirstAdapter.FirstViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FirstViewHolder { var inflateView = LayoutInflater.from(parent.context).inflate(R.layout.item_first, parent, false) return FirstViewHolder(inflateView) } override fun getItemCount(): Int { return dataList.size } override fun onBindViewHolder(holder: FirstViewHolder, position: Int) { holder.firstText.text = dataList[position] } class FirstViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var firstText: TextView init { firstText = itemView.findViewById<TextView>(R.id.tv_first) } } }
item_first
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="#f65478"
android:textColor="#ffffff"
android:id="@+id/tv_first"
android:text="First" />
</LinearLayout>
RecyclerView
的ItemView
之 - SecondAdapter
package com.example.concatadatper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class SecondAdapter(private val dataList: MutableList<String>) : RecyclerView.Adapter<SecondAdapter.SecondViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondViewHolder { var inflateView = LayoutInflater.from(parent.context).inflate(R.layout.item_second, parent, false) return SecondViewHolder(inflateView) } override fun getItemCount(): Int { return dataList.size } override fun onBindViewHolder(holder: SecondViewHolder, position: Int) { holder.secondText.text = dataList[position] } class SecondViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var secondText: TextView init { secondText = itemView.findViewById<TextView>(R.id.tv_second) } } }
item_second
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="#e98745"
android:textColor="#ffffff"
android:id="@+id/tv_second"
android:text="Second" />
</LinearLayout>
package com.example.concatadatper import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { private var firstDataList: MutableList<String> = mutableListOf<String>() private var secondDataList: MutableList<String> = mutableListOf<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initData() val firstAdapter = FirstAdapter(firstDataList) val secondAdapter = SecondAdapter(secondDataList) var config = ConcatAdapter.Config.Builder() .setIsolateViewTypes(true) .build() val concatAdapter = ConcatAdapter(config, firstAdapter, secondAdapter) val mRv = findViewById<RecyclerView>(R.id.rv) mRv.layoutManager = LinearLayoutManager(this) mRv.adapter = concatAdapter } /** * 数据模拟 * */ private fun initData() { for (data in 0..10) { firstDataList.add("First-Data-$data") } for (data in 0..10) { secondDataList.add("Second-Data-$data") } } }
activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.appcompat.widget.LinearLayoutCompat>
不知道你有没有像我一样的想法,能否通过ConcatAdapter
实现类似viewitem
穿插的效果?
期望效果:列表中子视图以ABA效果展示
伪代码:IsolateViewTypes = true
不共享缓存,可正常显示ABA
val firstAdapter = FirstAdapter(firstDataList)
val secondAdapter = SecondAdapter(secondDataList)
val firstAdapter1 = FirstAdapter(firstDataList)//重新创建一个对应的Adapter
var config = ConcatAdapter.Config.Builder()
.setIsolateViewTypes(true)
.build()
val concatAdapter = ConcatAdapter(config, firstAdapter, secondAdapter,firstAdapter1 )
伪代码:IsolateViewTypes = false
共享缓存,直接崩了,类型转换失败,错误如下图
val firstAdapter = FirstAdapter(firstDataList)
val secondAdapter = SecondAdapter(secondDataList)
var config = ConcatAdapter.Config.Builder()
.setIsolateViewTypes(false)
.build()
val concatAdapter = ConcatAdapter(config, firstAdapter, secondAdapter,firstAdapter)
有的人可能会想以 ConcatAdapter(config, firstAdapter, secondAdapter,secondAdapter)
方式添加,结果依旧如下
假设期望列表中子视图以ABA效果展示为AB,可能存在一些问题
如果同列表、同Adapter
再次添加到ConcatAdapter
后仅显示一次就需要设置IsolateViewTypes = false
,这样重复添加的Adapter直接读取了首个Adapter内的ViewHodler
缓存;虽然解决了重复问题,不过就会导致无法兼容另一个itemType
的Adapter
,真是鱼和熊掌不可兼得。当然可能有办法,只是我尚不自知~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。