本章前言
这篇文章是在讲解kotlin协程
的时候扩展而来,如果对kotlin协程
感兴趣的可以通过下面链接进行阅读、
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
笔者也只是一个普普通通的开发者,设计不一定合理,大家可以自行吸收文章精华,去糟粕。
现在我们就可以开始做一些基础的封装工作,同时在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()
}
override fun onDestroy() {
super.onDestroy()
//此处记得取消绑定,避免内存泄露
if(::mBinding.isInitialized){
mBinding.unbind()
}
}
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
进行处理呢。那还等什么,赶紧去实现吧。毕竟授人以鱼,不如授人以渔。
需要源码的看这里:demo源码
原创不易。如果您喜欢这篇文章,您可以动动小手点赞收藏。
Android技术交流群,有兴趣的可以私聊加入。
关联文章
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
今天的文章封装DataBinding让你少写万行代码分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22439.html