RecyclerView复杂适配器的终极形态?代码更解耦
?本文已授權微信公眾號:鴻洋??在微信公眾號平臺原創首發。RecyclerView復雜適配器的“終極形態”?代碼更解耦
前言
RecyclerView是Android開發中很常用的控件,市面上也有很多種封裝,使其更易用,但是面對復雜的適配器需求,則很難做到邏輯清晰且解耦,比如聊天消息的適配器
正文
1.首先我們用最原始的方法寫一個簡單的聊天消息的rv
實現圖如下:
代碼如下:
class MainActivity : AppCompatActivity() {//數據源: type to dataval msgData = mutableListOf<Pair<Int, Any>>(1 to "文本消息",2 to R.drawable.ic_launcher,//圖片消息3 to "tips消息",1 to "文本消息2",4 to "不支持的消息",)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//初始化rvval rv = RecyclerView(this)setContentView(rv)val llm = LinearLayoutManager(this)llm.orientation = LinearLayoutManager.VERTICALrv.layoutManager = llm//適配器rv.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): RecyclerView.ViewHolder {//根據不同的viewType創建不同的viewHolderreturn when (viewType) {1 -> TextVH(parent)2 -> ImageVH(parent)3 -> TipsVH(parent)else -> NoMatchVH(parent)}}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {//判斷相應type,并根據數據展示相應的viewwhen (getItemViewType(position)) {1 -> {(holder.itemView as TextView).text = msgData[position].second.toString()}2 -> {(holder.itemView as ImageView).setImageResource(msgData[position].second as Int)}3 -> {(holder.itemView as TextView).text = msgData[position].second.toString()}}}override fun getItemCount(): Int = msgData.size//條目的數量override fun getItemViewType(position: Int): Int =msgData[position].first//條目的type,使用數據源的type}}//文字的vhclass TextVH(parent: ViewGroup) : RecyclerView.ViewHolder(TextView(parent.context).apply {textSize = 22f})//圖片的vhclass ImageVH(parent: ViewGroup) : RecyclerView.ViewHolder(ImageView(parent.context))//提示性文字的vhclass TipsVH(parent: ViewGroup) : RecyclerView.ViewHolder(TextView(parent.context))//沒有匹配到現有type的vh,可能是新版本的消息class NoMatchVH(parent: ViewGroup) : RecyclerView.ViewHolder(TextView(parent.context)) }這時我們發現,設置view數據都集中在了onBindViewHolder方法中,類型多了或者邏輯多了后會造成方法非常臃腫,所以我們抽出來一個BaseViewolder出來,將設置view數據的方法抽出來,代碼如下:
class MainActivity : AppCompatActivity() {//數據源: type to dataval msgData = mutableListOf<Pair<Int, Any>>(1 to "文本消息",2 to R.drawable.ic_launcher,//圖片消息3 to "tips消息",1 to "文本消息2",4 to "不支持的消息",)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//初始化rvval rv = RecyclerView(this)setContentView(rv)val llm = LinearLayoutManager(this)llm.orientation = LinearLayoutManager.VERTICALrv.layoutManager = llm//適配器rv.adapter = object : RecyclerView.Adapter<BaseVH>() {override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): BaseVH {//根據不同的viewType創建不同的viewHolderreturn when (viewType) {1 -> TextVH(parent)2 -> ImageVH(parent)3 -> TipsVH(parent)else -> NoMatchVH(parent)}}override fun onBindViewHolder(holder: BaseVH, position: Int) { //**************調用setData方法讓viewHolder根據數據展示相應的viewholder.setData(msgData[position].second)}override fun getItemCount(): Int = msgData.size//條目的數量override fun getItemViewType(position: Int): Int =msgData[position].first//條目的type,使用數據源的type}}//******統一封裝vh的setData方法,使其解耦abstract class BaseVH(val parent: ViewGroup, val view: View) :RecyclerView.ViewHolder(view) {abstract fun setData(data: Any)}//文字的vhclass TextVH(parent: ViewGroup) : BaseVH(parent, TextView(parent.context).apply {textSize = 22f}) {override fun setData(data: Any) {(view as TextView).text = data as String}}//圖片的vhclass ImageVH(parent: ViewGroup) : BaseVH(parent, ImageView(parent.context)) {override fun setData(data: Any) {(view as ImageView).setImageResource(data as Int)}}//提示性文字的vhclass TipsVH(parent: ViewGroup) : BaseVH(parent, TextView(parent.context)) {override fun setData(data: Any) {(view as TextView).text = data as String}}//沒有匹配到現有type的vh,可能是新版本的消息class NoMatchVH(parent: ViewGroup) : BaseVH(parent, TextView(parent.context)) {override fun setData(data: Any) {(view as TextView).text = "暫不支持的消息,請更新應用"}} }但是看到上面創建viewholder的方法onCreateViewHolder,新增或修改type還是得每次自己手動修改
所以我們可以使用一個map,以type為key,并將需要創建的viewHolder的構造傳進去,如下:
//裝type:viewHolder構造的mapval viewHolderMap = HashMap<Int, (ViewGroup) -> BaseVH>().apply {put(1, ::TextVH)put(2, ::ImageVH)put(3, ::TipsVH)}//并修改創建viewHolder的方法override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): BaseVH {//根據不同的viewType創建不同的viewHolderreturn (viewHolderMap[viewType] ?: ::NoMatchVH).invoke(parent)}?這樣改造后,adapter中幾乎就沒有什么代碼了,代碼都移動到了各自的viewHolder中了,但是在新增viewHolder類型的時候這個viewHolderMap比較容易被忽略,能不能讓他自動將所有BaseVH的子類構造和相應的type添加進去呢?
java能通過反射拿到一個類所有的子類嗎?能也不能,實現起來很麻煩,而且在安卓系統上不一定行得通,但是kotlin反射可以
kotlin反射可以獲取密封類(sealed class)的所有直接子類,所以我們可以改造下
將BaseVH改為sealed class,然后在初始化adapter的時候通過kotlin的反射來獲取所有直接子類的構造(或class)
ps:使用kotlin的反射需要引入反射包
//kotlin反射implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")然后修改成如下代碼:
class MainActivity : AppCompatActivity() {//數據源: type to dataval msgData = mutableListOf<Pair<Int, Any>>(1 to "文本消息",2 to R.drawable.ic_launcher,//圖片消息3 to "tips消息",1 to "文本消息2",4 to "不支持的消息",)companion object {//******裝type:viewHolder構造的mapval viewHolderMap = HashMap<Int, Constructor<out BaseVH>>()init {val fl = FrameLayout(App.instance)//******遍歷class并構建出對象獲取其type,并自動注冊到viewHolderMap中BaseVH::class.sealedSubclasses.forEach {val constructor = it.java.getConstructor(ViewGroup::class.java)val baseVH = constructor.newInstance(fl)viewHolderMap[baseVH.type] = constructor}}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//初始化rvval rv = RecyclerView(this)setContentView(rv)val llm = LinearLayoutManager(this)llm.orientation = LinearLayoutManager.VERTICALrv.layoutManager = llm//適配器rv.adapter = object : RecyclerView.Adapter<BaseVH>() {override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): BaseVH { //**************根據不同的viewType創建不同的viewHolderreturn viewHolderMap[viewType]?.newInstance(parent) ?: NoMatchVH(parent)}override fun onBindViewHolder(holder: BaseVH, position: Int) {//調用setData方法讓viewHolder根據數據展示相應的viewholder.setData(msgData[position].second)}override fun getItemCount(): Int = msgData.size//條目的數量override fun getItemViewType(position: Int): Int =msgData[position].first//條目的type,使用數據源的type}}//統一封裝vh的setData方法,使其解耦sealed class BaseVH(val parent: ViewGroup, val view: View) :RecyclerView.ViewHolder(view) {abstract val type: Intabstract fun setData(data: Any)}//文字的vhclass TextVH(parent: ViewGroup) : BaseVH(parent, TextView(parent.context).apply {textSize = 22f}) {override val type = 1override fun setData(data: Any) {(view as TextView).text = data as String}}//圖片的vhclass ImageVH(parent: ViewGroup) : BaseVH(parent, ImageView(parent.context)) {override val type = 2override fun setData(data: Any) {(view as ImageView).setImageResource(data as Int)}}//提示性文字的vhclass TipsVH(parent: ViewGroup) : MainActivity.BaseVH(parent, TextView(parent.context)) {override val type = 3override fun setData(data: Any) {(view as TextView).text = data as String}}//沒有匹配到現有type的vh,可能是新版本的消息class NoMatchVH(parent: ViewGroup) : MainActivity.BaseVH(parent, TextView(parent.context)) {override val type = 99999override fun setData(data: Any) {(view as TextView).text = "暫不支持的消息,請更新應用"}} }這樣改造后,就可以直接繼承BaseVH,并重寫一下type,就可以自動注冊了,減少大腦負擔
ps:在BaseVH中加一個abstract的type,這樣子類不重寫type就編譯不過去,但是需要統一構造的入參類型,并且有點騷操作; 如果不想這樣處理的話,可以將type改為靜態的,然后可以直接通過class反射獲取type的值,或將type放到類名上(騷操作).但后兩種方法是沒有編譯時提醒的,很容易忘.
結語
上面的方案就是我認為的RecyclerView復雜適配器且更解耦的終極形態.
擴展
要問還有更好的方式嗎?
我覺得有!那就是使用compose,當然目前用compose的人或公司很少,但也是一種比較超前的而且我覺得是未來的終極形態,代碼如下:
//數據源: type to dataval msgData = mutableListOf<Pair<Int, Any>>(1 to "文本消息",2 to R.drawable.ic_launcher,//圖片消息3 to "tips消息",1 to "文本消息2",4 to "不支持的消息",)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//compose的豎向RecyclerViewLazyColumn(Modifier.fillMaxSize()) {//遍歷數據源,展示數據對應的viewmsgData.forEach {item {//根據type的不同展示不同的view(ItemFunctions.itemFunctionMap[1]?: ItemFunctions::NoMatchItem_99999).invoke(it.second)}}}}}object ItemFunctions {@Composablefun TextItem_1(any: Any) {Text(text = any as String, fontSize = 22.sp)}@Composablefun ImgItem_2(any: Any) {Image(painter = painterResource(id = any as Int), contentDescription = "")}@Composablefun TipsItem_3(any: Any) {Text(text = any as String)}@Composablefun NoMatchItem_99999(any: Any) {Text(text = "暫不支持的消息,請更新應用")}@JvmStaticval itemFunctionMap = HashMap<Int, @Composable (Any) -> Unit>()init {ItemFunctions::class.functions.filter { it.javaMethod?.declaringClass == ItemFunctions::class.java }.forEach {val type = it.name.split('_').lastOrNull()?.toIntOrNull() ?: return@forEachitemFunctionMap[type] = it as @Composable (Any) -> Unit}}}ps:這里使用了騷操作,將type放在了方法名的末尾
pps:這段代碼目前是運行不起來的,因為kotlin暫時還不支持引用@Composable的Function,但能運行起來也是和上面View展示的一樣的效果
ppps:當然也可以在msgData.forEach{}內用when來做,但后續type都得手動添加,如下:
//數據源: type to dataval msgData = mutableListOf<Pair<Int, Any>>(1 to "文本消息",2 to R.drawable.ic_launcher,//圖片消息3 to "tips消息",1 to "文本消息2",4 to "不支持的消息",)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//compose的豎向RecyclerViewLazyColumn(Modifier.fillMaxSize()) {//遍歷數據源,展示數據對應的viewmsgData.forEach {item {//根據type的不同展示不同的viewwhen (it.first) {1 -> TextItem(any = it.second)2 -> ImgItem(any = it.second)3 -> TipsItem(any = it.second)else -> NoMatchItem()}}}}}}@Composablefun TextItem(any: Any) {Text(text = any as String, fontSize = 22.sp)}@Composablefun ImgItem(any: Any) {Image(painter = painterResource(id = any as Int), contentDescription = "")}@Composablefun TipsItem(any: Any) {Text(text = any as String)}@Composablefun NoMatchItem() {Text(text = "暫不支持的消息,請更新應用")}end
總結
以上是生活随笔為你收集整理的RecyclerView复杂适配器的终极形态?代码更解耦的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 超简单-用协程简化你的网络请求吧,兼容你
- 下一篇: 使用Kotlin写脚本