笔者也只是一个普普通通的开发者,设计不一定合理,大家可以自行吸收文章精华,去糟粕。
现在我们就可以开始做一些基础的封装工作,同时在app的bulid.gradle
文件中开启dataBinding
的使用
android { buildFeatures { dataBinding = true } //省略... } 复制代码
DataBinding
的封装我们先创建一个简单的布局文件activity_main.xml
。为了节约时间,同时我们也创建一个fragment_main.xml
保持一样的布局。
<?xml version="1.0" encoding="utf-8"?> <layout 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"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.MainActivity"> <Button android:id="@+id/btn" android:layout_width="100dp" android:layout_height="50dp" android:gravity="center" android:text="Hello World" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> 复制代码
我们在使用DataBinding
初始化布局时候,我们通常喜欢使用下面几种方式, 在Activity
中:
class MainActivity : AppCompatActivity() { private lateinit var mBinding:ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main) } } 复制代码
在Fragment
中:
class HomeFragment:Fragment() { private lateinit var mBinding:FragmentMainBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mBinding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_main,container,false) return mBinding.root } } 复制代码
这种情况下,每创建一个activity
和Fragment
都需要重写一遍。所以我们创建一个BaseActivity
进行抽象,然后使用泛型传入我们需要的ViewDataBinding
对象VB
,再通过构造方法或者抽象方法获取LayoutRes
资源
abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes resId:Int) : AppCompatActivity() { lateinit var mBinding:VB override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBinding = DataBindingUtil.setContentView<VB>(this,resId) } //... } //或者 abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity() { lateinit var mBinding:VB @LayoutRes abstract fun getLayoutId():Int override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBinding = DataBindingUtil.setContentView<VB>(this,getLayoutId()) } } 复制代码
这个时候是不是经过我们经过上面处理后,再使用的时候我们会方便很多。可能有些人封装过的人看到这里会想,你讲的这都是啥,这些我都会,没有一点创意。笔者想说:不要捉急,我们要讲的可不是上面的东西,毕竟做事情都需要前奏铺垫滴。
虽然经过上面的抽象以后,我们是减少了一些步骤。但是笔者还写觉得有些麻烦,因为我们还是需要手写的通过外部传一个LayoutRes
资源才能进行使用。想要再次细化缩减代码,那我们就得看看ActivityMainBinding
的实现。
我们在开启DataBinding
的时候,通过使用layout
的activity_main.xml
布局,DataBinding
在编译的时候会自动在我们的工程app/build/generated/data_binding_base_class_source_out/packname/databinding
目录下为我们生成一个ActivityMainBinding
类,我们看看它的实现:
public abstract class ActivityMainBinding extends ViewDataBinding { @NonNull public final Button btn; protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount, TextView tv) { super(_bindingComponent, _root, _localFieldCount); this.btn = btn; this.recyclerView = recyclerView; } public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) { return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent()); } public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) { return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component); } public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) { return inflate(inflater, DataBindingUtil.getDefaultComponent()); } public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable Object component) { return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component); } //省略... } 复制代码
我们可以看到在ActivityMainBinding
中的有4个inflate
方法,同时他们最后的都会直接使用我们的布局文件activity_main.xml
进行加载。所以我们想在上面的基础上进一步的简化使用方式,我们就必须通过反射的机制,从拿到ActivityMainBinding
中的inflate
方法,使用相对应的inflate
方法去加载我们的布局。代码如下:
inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater):VB{ val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java) return inflate.invoke(null, inflater) as VB } 复制代码
我们先定义个扩展方法,通过反射的方式,从我们的Class
中拿到我们想要的泛型类ViewBinding
,然后invoke
调用ViewBinding
的inflate
方法。然后我们再创建一个接口用于BaseActivity
子类进行UI初始化绑定操作。
interface BaseBinding<VB : ViewDataBinding> { fun VB.initBinding() } 复制代码
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity(), BaseBinding<VB> { internal val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { getViewBinding(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(mBinding.root) mBinding.initBinding() } } 复制代码
现在我们就可以继承BaseActivity
实现我们的Activity
:
class MainActivity : BaseActivity<ActivityMainBinding>() { override fun ActivityMainBinding.initBinding() { Log.d("MainActivity","btn :${btn.text}") } } 复制代码
D/MainActivity: btn :Hello World 复制代码
现在我们的代码是不是变得简洁、清爽很多。这样我们不仅节省了编写大量重复代码的时间,同时也让我们代码的变得更加合理、美观。
和Activity
有一些不同,因为Fragment
创建布局的时候需要传入ViewGroup
,所以我们稍微做一个变化。
inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?):VB{ val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java) return inflate.invoke(null, inflater, container, false) as VB } 复制代码
可能在某些环境下有些人在创建Fragment
的时候需要把attachToRoot
设置成true
,这个时候自己扩展一个就好了,我们这里就不再演示。同理我们再抽象一个BaseFragment
:
abstract class BaseFragment<VB : ViewDataBinding>: Fragment(),BaseBinding<VB> { protected lateinit var mBinding:VB private set override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mBinding = getViewBinding(inflater,container) return mBinding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mBinding.initBinding() } } 复制代码
class HomeFragment:BaseFragment<FragmentMainBinding>() { override fun FragmentMainBinding.initBinding() { Log.d("HomeFragment","btn :${btn.text}") } } 复制代码
看到这里是不是心灵舒畅了很多,不仅代码量减少了,逼格也提升了许多,同时又增加了XX技术群
里摸鱼吹水的时间。
当然我们也不能仅仅满足于此,在码代码的过程中还有一个大量重复工作的就是我们的Adapter
,我们就拿使用到做多的RecyclerView.Adapter
为例,假设我们创建一个最简单的HomeAdapter
:
class HomeAdapter(private val data: List<String>? = null) : RecyclerView.Adapter<HomeAdapter.BindingViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { val mBinding = DataBindingUtil.inflate<ItemHomeBinding>(LayoutInflater.from(parent.context), R.layout.item_home ,parent, false) return BindingViewHolder(mBinding) } override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { holder.binding.tv.text = data?.get(position) ?: "" //其他绑定... holder.binding.executePendingBindings() } fun setData(){ //刷新数据... } override fun getItemCount(): Int { return data?.size ?: 0 } class BindingViewHolder constructor(val binding: ItemHomeBinding) : RecyclerView.ViewHolder(binding.root) } 复制代码
就这样一个最简单的Adapter
,我们都需要些一堆啰嗦代码,如果再加上item
的click
事件的话,我们要做的 工作就变得更多。那么我们现在要解决这么一个问题的话,我们要处理哪里东西呢:
Adapter
的初始化工作。onBindViewHolder
的使用。ViewHolder
。Item
的监听事件方式。Adapter
的数据刷新。首先我们需要修改一下我们之前定义的扩展getViewBinding
,因为我们是不知道具体这个getViewBinding
是用在哪个类上,这个类又定义了几个泛型。所以我们增加一个默认值为0
的position
参数代替之前写死的0
,通过这种方式让调用者自行设定VB:ViewBinding
所在的位置顺序:
inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater,position:Int = 0):VB{ val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java) return inflate.invoke(null, inflater) as VB } inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?,position:Int = 0):VB{ val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java) return inflate.invoke(null, inflater, container, false) as VB } 复制代码
创建我们的BaseAdapter
,先将完整代码贴出:
abstract class BaseAdapter<T, VB : ViewDataBinding> : RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() { private var mData: List<T> = mutableListOf() fun setData(data: List<T>?) { data?.let { val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int { return mData.size } override fun getNewListSize(): Int { return it.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseAdapter.areItemsTheSame(oldData, newData) } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition) } }) mData = data result.dispatchUpdatesTo(this) } ?: let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size) } } fun addData(data: List<T>?, position: Int? = null) { if (!data.isNullOrEmpty()) { with(LinkedList(mData)){ position?.let { val startPosition = when { it < 0 -> 0 it >= size -> size else -> it } addAll(startPosition, data) }?: addAll(data) setData(this) } } } protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean { return oldItem == newItem } protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem == newItem } fun getData(): List<T> { return mData } fun getItem(position: Int): T { return mData[position] } fun getActualPosition(data: T): Int { return mData.indexOf(data) } override fun getItemCount(): Int { return mData.size } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> { return with(getViewBinding<VB>(LayoutInflater.from(parent.context), parent,1)) { setListener() BindViewHolder(this) } } override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) { with(holder.binding){ onBindViewHolder(getItem(position), position) executePendingBindings() } } open fun VB.setListener() {} abstract fun VB.onBindViewHolder(bean: T, position: Int) class BindViewHolder<M : ViewDataBinding>(var binding: M) : RecyclerView.ViewHolder(binding.root) } 复制代码
我们这里先忽略这个BaseAdapter
的定义,现在我们将HomeAdapter
修改一下就变成了:
class HomeAdapter : BaseAdapter<String, ItemHomeBinding>() { override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) { tv.text = bean } } 复制代码
我们在Activity
中使用时:
class MainActivity : BaseActivity<ActivityMainBinding>() { lateinit var homeAdapter:HomeAdapter override fun ActivityMainBinding.initBinding() { homeAdapter = HomeAdapter() with(recyclerView){ layoutManager = LinearLayoutManager(this@MainActivity).apply { orientation = RecyclerView.VERTICAL } adapter = homeAdapter } homeAdapter.setData(listOf("a","b","c","d","e","f")) } } 复制代码
现在我们的adapter
中的代码是不是变的超简洁,我相信现在即使让你再写100
个Adapter
也不害怕。
现在我们来一步一步的拆解BaseAdapter
。我们看到BaseAdapter
需要传入2个泛型,T
是我们需要显示的实体的数据类型,VB
是我们的布局绑定ViewDataBinding
。
abstract class BaseAdapter<T, VB : ViewDataBinding> : RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() { //... } 复制代码
往下可以看到我们通过在BaseAdapter
实现onCreateViewHolder
来处理Item
布局的初始化工作,我们这里调用getViewBinding
的时候position
传入的是1
,正好对应我们VB
所在的顺序:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> { return with(getViewBinding<VB>(LayoutInflater.from(parent.context), parent,1)) { setListener() BindViewHolder(this) } } 复制代码
同时我们创建了一个内部类BindViewHolder
来进行统一ViewHolder
的创建工作。
class BindViewHolder<M : ViewDataBinding>(var binding: M) : RecyclerView.ViewHolder(binding.root) 复制代码
我们在初始化布局的同时又调用了一个空实现的setListener
方法。为什么我们在这里采用open
而不是采用abstract
来定义。是因为我们不是每一个Adapter
都需要设置Item
的监听事件,因此我们把setListener
只是作为一个可选的项来处理。
open fun VB.setListener() {} 复制代码
初始化完成以后,我们需要进行布局绑定,但是因为不同的Adapter
的界面,需要处理的绑定是不一样的,所以我们在实现onBindViewHolder
的同时,通过调用内部创建的抽象方法VB.onBindViewHolder
将我们的绑定处理交由子类进行处理。
override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) { with(holder.binding){ onBindViewHolder(getItem(position), position) executePendingBindings() } } 复制代码
同时将VB.onBindViewHolder
参数转换为实际的数据bean
和对应的位置position
.
abstract fun VB.onBindViewHolder(bean: T, position: Int) 复制代码
到目前为止,我们在BaseAdapter
中已经处理了:
Adapter
的初始化工作。onBindViewHolder
的使用。ViewHolder
。Item
的监听事件方式。现在我们就来看下是如何统一Adapter
的数据刷新。可以看到我们在BaseAdapter
创建了一个私有数据集合mData
,在mData
中存放的是我们需要显示的泛型T
的数据类型。
private var mData: List<T> = mutableListOf() 复制代码
同时我们增加了一个setData
方法,在此方法中我们使用DiffUtil
对我们的数据进行对比刷新。,如果对DiffUtil
不太熟的可以查一下它的方法。
fun setData(data: List<T>?) { data?.let { val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int { return mData.size } override fun getNewListSize(): Int { return it.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseAdapter.areItemsTheSame(oldData, newData) } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition) } }) mData = data result.dispatchUpdatesTo(this) } ?: let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size) } } 复制代码
这里我们需要注意一下,DiffUtil.Callback
中的areItemsTheSame
和areContentsTheSame
2个对比数据的方法,实际上是通过我们在BaseAdapter
中定义2个open
方法areItemContentsTheSame
,areItemsTheSame
。
protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean { return oldItem == newItem } protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem == newItem } 复制代码
为什么要这么去定义的呢。虽然在BaseAdapter
中实现了这2个方法,因为我们不知道子类在实现的时候是否需要改变对比的方式。比如我在使用areItemsTheSame
的时候,泛型T
如果泛型T不是一个基本数据类型,通常只需要对比泛型T
中的唯一key
就可以。现在假设泛型T
是一个数据实体类User
:
data class User(val id:Int,val name:String) 复制代码
那我们在子类复写areItemsTheSame
方法的时候,就可以在我们的实现的apapter
如下使用:
protected open fun areItemsTheSame(oldItem: User, newItem: User): Boolean { return oldItem.id == newItem.id } 复制代码
细心的可能注意到我们有定义一个getActualPosition
方法,为什么不是叫getPosition
。这是因为在有些为了方便,我们在onBindViewHolder
的时候把此时position
保存下来,或者设置监听器的时候传入了position
。
如果我们在之前的数据基础上插入或者减少几条数据的话,但是又因为我们使用了DiffUtil
的方式去刷新,由于之前已存在bean
的数据没变,只是位置变了,所以onBindViewHolder
不会执行,这个时候我们直接使用position
的时候会出现位置不对问题,或者是越界的问题。比如如下使用:
interface ItemClickListener<T> { fun onItemClick(view: View,position:Int, data: T){} fun onItemClick(view: View, data: T) } 复制代码
我们在ItemClickListener
定义了2个方法,我们使用带有position
的onItemClick
方法来演示:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="bean" type="String" /> <variable name="position" type="int" /> <variable name="itemClickListener" type="com.carman.kotlin.coroutine.interf.ItemClickListener" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="50dp" android:onClick="@{(v)->itemClickListener.onItemClick(v,position,bean)}" android:textColor="@color/black" android:textSize="18sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> 复制代码
我们在XML中进行Click
绑定,然后我们在HomeAdapter
进行监听事件和数据设置
class HomeAdapter(private val listener: ItemClickListener<String>) : BaseAdapter<String, ItemHomeBinding>() { override fun ItemHomeBinding.setListener() { itemClickListener = listener } override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) { this.bean = bean this.position = position tv.text = bean } } 复制代码
接下来我们在MainActivity
通过2次设置数据在点击查看日志
class MainActivity : BaseActivity<ActivityMainBinding>() { lateinit var homeAdapter:HomeAdapter override fun ActivityMainBinding.initBinding() { homeAdapter = HomeAdapter(itemClickListener) with(recyclerView){ layoutManager = LinearLayoutManager(this@MainActivity).apply { orientation = RecyclerView.VERTICAL } adapter = homeAdapter } homeAdapter.setData(listOf("a","b","c","d","e","f")) btn.setOnClickListener { Log.d("刷新", "第二次setData") homeAdapter.setData(listOf("c","d","e","f")) } } private val itemClickListener = object : ItemClickListener<String> { override fun onItemClick(view: View, position: Int, data: String) { Log.d("onItemClick", "data:$data position:$position") } } } 复制代码
D/onItemClick: data:a position:0 D/onItemClick: data:b position:1 D/onItemClick: data:c position:2 D/onItemClick: data:d position:3 D/onItemClick: data:e position:4 D/onItemClick: data:f position:5 D/刷新: 第二次setData D/onItemClick: data:c position:2 D/onItemClick: data:d position:3 D/onItemClick: data:e position:4 D/onItemClick: data:f position:5 复制代码
所以我们需要在使用position
的时候,最好是通过getActualPosition
来获取真实的位置,我们修改一下itemClickListener
中的日志输出。
private val itemClickListener = object : ItemClickListener<String> { override fun onItemClick(view: View, position: Int, data: String) { Log.d("onItemClick", "data:$data position:${homeAdapter.getActualPosition(data)}") } } 复制代码
这个时候我们再重复上面操作的时候,就可以看到position
的位置就是它目前所处的真实位置。
D/onItemClick: data:c position:0 D/onItemClick: data:d position:1 D/onItemClick: data:e position:2 D/onItemClick: data:f position:3 复制代码
到此为止,我们对于这个BaseAdapter<T, VB : ViewDataBinding>
的抽象原理,以及使用方式有了大概的了解。
需要注意的是:为了方便简单演示,我们这里假设是,没有在xml中直接使用Databinding
进行绑定。因为有些复杂逻辑我们是没有办法简单的在xml中进行绑定的。
很显然我们的工作并没有到此结束,因为我们的adapter
在常用的场景中还有多布局的情况,那我们又应该如何处理呢。
这个其实很好办。因为我们是多布局,那么就意味着我们需要把onCreateViewHolder
中的一部分工作暴露给子类处理,所以我们需要在上面BaseAdapter
的基础上做一些修改。照例上代码:
abstract class BaseMultiTypeAdapter<T> : RecyclerView.Adapter<BaseMultiTypeAdapter.MultiTypeViewHolder>() { private var mData: List<T> = mutableListOf() fun setData(data: List<T>?) { data?.let { val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int { return mData.size } override fun getNewListSize(): Int { return it.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseMultiTypeAdapter.areItemsTheSame(oldData, newData) } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldData: T = mData[oldItemPosition] val newData: T = it[newItemPosition] return this@BaseMultiTypeAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition) } }) mData = data result.dispatchUpdatesTo(this) } ?: let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size) } } fun addData(data: List<T>?, position: Int? = null) { if (!data.isNullOrEmpty()) { with(LinkedList(mData)) { position?.let { val startPosition = when { it < 0 -> 0 it >= size -> size else -> it } addAll(startPosition, data) } ?: addAll(data) setData(this) } } } protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean { return oldItem == newItem } protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem == newItem } fun getData(): List<T> { return mData } fun getItem(position: Int): T { return mData[position] } fun getActualPosition(data: T): Int { return mData.indexOf(data) } override fun getItemCount(): Int { return mData.size } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiTypeViewHolder { return MultiTypeViewHolder(onCreateMultiViewHolder(parent, viewType)) } override fun onBindViewHolder(holder: MultiTypeViewHolder, position: Int) { holder.onBindViewHolder(holder, getItem(position), position) holder.binding.executePendingBindings() } abstract fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: T, position: Int) abstract fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding protected fun <VB :ViewDataBinding> loadLayout(vbClass: Class<VB>,parent: ViewGroup): VB { val inflate = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java) return inflate.invoke(null, LayoutInflater.from(parent.context), parent, false) as VB } class MultiTypeViewHolder(var binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) } 复制代码
通过上面的代码可以看到,我们没有在BaseMultiTypeAdapter
中定义泛型VB :ViewDataBinding
,因为我们是多布局,如果都写在类的定义中明显是不合适的,我们也不知道在具体实现需要有多少个布局。
所以我们onCreateViewHolder
初始化布局的时候调用了一个抽象的onCreateMultiViewHolder
方法,这个方法交由我们具体业务实现类去实现。同时我们对onBindViewHolder
进行修改,增加了一个holder
参数供外部使用。 我们先数据实体类型
sealed class Person(open val id :Int, open val name:String) data class Student( override val id:Int, override val name:String, val grade:String):Person(id, name) data class Teacher( override val id:Int, override val name:String, val subject:String):Person(id, name) 复制代码
和我们需要实现的Adapter业务类,:
class SecondAdapter: BaseMultiTypeAdapter<Person>() { companion object{ private const val ITEM_DEFAULT_TYPE = 0 private const val ITEM_STUDENT_TYPE = 1 private const val ITEM_TEACHER_TYPE = 2 } override fun getItemViewType(position: Int): Int { return when(getItem(position)){ is Student -> ITEM_STUDENT_TYPE is Teacher -> ITEM_TEACHER_TYPE else -> ITEM_DEFAULT_TYPE } } override fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding { return when(viewType){ ITEM_STUDENT_TYPE -> loadLayout(ItemStudentBinding::class.java,parent) ITEM_TEACHER_TYPE -> loadLayout(ItemTeacherBinding::class.java,parent) else -> loadLayout(ItemPersionBinding::class.java,parent) } } override fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: Person, position: Int) { when(holder.binding){ is ItemStudentBinding ->{ Log.d("ItemStudentBinding","item : $item position : $position") } is ItemTeacherBinding ->{ Log.d("ItemTeacherBinding","item : $item position : $position") } } } } 复制代码
class MainActivity : BaseActivity<ActivityMainBinding>() { override fun ActivityMainBinding.initBinding() { val secondAdapter = SecondAdapter() with(recyclerView){ layoutManager = LinearLayoutManager(this@MainActivity).apply { orientation = RecyclerView.VERTICAL } adapter = secondAdapter } secondAdapter.setData( listOf( Teacher(1,"Person","语文"), Student(2,"Person","一年级"), Teacher(3,"Person","数学"), )) } 复制代码
运行一下就可以看到我们想要的结果:
D/ItemTeacherBinding: item : Teacher(id=1, name=Person, subject=语文) position : 0 D/ItemStudentBinding: item : Student(id=2, name=Person, grade=一年级) position : 1 D/ItemTeacherBinding: item : Teacher(id=3, name=Person, subject=数学) position : 2 复制代码
经过上面的处理以后,我们在创建Activiy
、Fragment
、Adapter
的时候减少了大量的代码。同时也节省了码这些重复垃圾代码的时间,起码让你们的工作效率起码提升10
个百分点,是不是感觉到自己无形中又变帅了许多。
经过以上封装处理以后,我们是不是也可以对Dialog
,PopWindow
、动态初始化View
进行处理呢。那还等什么,赶紧去实现吧。毕竟授人以鱼,不如授人以渔。
作者:一个被摄影耽误的程序猿
链接:https://juejin.cn/post/6957608813809795108
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。