如何在项目中封装 Kotlin + Jetpack Databinding
这篇文章主要来分析如何基于 DataBinding 封装 DataBindingActivity
、DataBindingFragment
、DataBindingDialog
、 DataBindingListAdapter
等等,成员陆续增加中,代码已经上传到 GitHub 欢迎前去查看仓库 JDataBinding
。
JDataBinding
https://github.com/hi-dhl/JDataBinding
什么是 DataBinding?
DataBinding 是 Google 在 Jetpack 中推出的一个数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源。
利用 Kotlin 中的 inline
、 reified
、 DSL
等等语法, 结合着 DataBinding,可以设计出更加简洁并利于维护的代码,首先我们先来看一下 DataBinding 在 ListAdapter
中的使用。
DataBindingListAdapter
DataBindingListAdapter
是基于 ListAdapter 封装的,使用更少的代码快速实现 RecyclerView adapter 和 ViewHolder。
什么是 ListAdapter?
ListAdapter 是 Google 推出的一个新的类库,相比传统的 Adapter,它能够用较少的代码实现更多的 RecylerView 的动画,并且可以自动存储之前的 list,ListAdapter 还加入了 DiffUtil 的工具类,只有当 items 变化的时候进行刷新,而不用刷新整个 list,大大提高 RecyclerView 的性能。
什么是 DiffUtil?
DiffUtil 主要在后台计算 list 是否相同,然后回到回主线程刷新数据,主要使用了 Myers Diff Algorithm
算法, 而我们日常使用的 git diff 就用到了该算法。
好了介绍完基础概念之后,来看一下 DataBindingListAdapter
是如何使用的。
Step1: 继承 BaseViewHolder
创建一个自定义的 ViewHolder 类,继承 BaseViewHolder
,通过 viewHolderBinding
可以快速实现 DataBinding 的绑定。
class TestViewHolder(view: View) : BaseViewHolder<Model>(view) {
val binding: RecycieItemTestBinding by viewHolderBinding(view)
override fun bindData(data: Model) {
binding.apply {
model = data
executePendingBindings()
}
}
}
Step2: 继承 DataBindingListAdapter
实现带头部和尾部的 Adapter,创建自定义的 Adapter,继承 DataBindingListAdapter
class TestAdapter : DataBindingListAdapter<Model>(Model.CALLBACK) {
override fun viewHolder(layout: Int, view: View): DataBindingViewHolder<Model> = when (layout) {
R.layout.recycie_item_header -> HeaderViewHolder(view)
else -> TestViewHolder(view)
}
override fun layout(position: Int): Int = when (position) {
0 -> R.layout.recycie_item_header
getItemCount() - 1 -> R.layout.recycie_item_footer
else -> R.layout.recycie_item_test
}
override fun getItemCount(): Int = super.getItemCount() + 2
}
构造方法需要传入了 Model.CALLBACK
,Model.CALLBACK
实现了 DiffUtil.ItemCallback
,用于计算 list 的两个非空 item 的不同。需要重写两个抽象方法 areItemsTheSame
和 areContentsTheSame
。
val CALLBACK: DiffUtil.ItemCallback<Model> = object : DiffUtil.ItemCallback<Model>() {
// 判断两个Objects 是否代表同一个item对象, 一般使用Bean的id比较
override fun areItemsTheSame(oldItem: Model, newItem: Model): Boolean =
oldItem.id == newItem.id
// 判断两个Objects 是否有相同的内容。
override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean = true
}
Step3: 绑定 RecyclerView 和 Adapter
<data>
<variable
name="viewModel"
type="com.hi.dhl.jdatabinding.demo.ui.MainViewModel" />
<variable
name="testAdapter"
type="com.hi.dhl.jdatabinding.demo.ui.TestAdapter" />
</data>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:adapter="@{testAdapter}"
app:adapterList="@{viewModel.mLiveData}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
这里用到了 DataBinding 的自定义数据绑定部分,具体实现可以参考仓库 JDataBinding 中的 fragment_test.xml
文件。
DataBindingDialog
在 Kotlin 中应该尽量避免使用构建者模式,使用 Kotlin 的具名可选参数构造类,实现构建者模式,代码更加简洁。
在 "Effective Java" 书中介绍构建者模式时,是这样子描述它的:本质上 builder 模式模拟了具名的可选参数,就像 Ada和 Python中的一样。
幸运的是,Kotlin 是一门拥有具名可选参数的变成语言,DataBindingDialog
通过使用 Kotlin 的具名可选参数构造类,实现了 Dailog 构建者模式,用 DataBinding 进行二次封装,加上 DataBinding 数据绑定的特性,使 Dialog 变得更加简洁、易用。
Step1: 继承 DataBindingDialog
class AppDialog(
context: Context,
val title: String? = null,
val message: String? = null,
val yes: AppDialog.() -> Unit
) : DataBindingDialog(context, R.style.AppDialog) {
private val mBinding: DialogAppBinding by binding(R.layout.dialog_app)
init {
requireNotNull(message) { "message must be not null" }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
mBinding.apply {
setContentView(root)
display.text = message
btnNo.setOnClickListener { dismiss() }
btnYes.setOnClickListener { yes() }
}
}
}
Step2: 简洁的调用方式
AppDialog(
context = this@MainActivity,
message = msg,
yes = {
// do something
}).show()
DataBindingActivity
Kotlin 中的函数和构造器都支持具名可选参数,在使用上更加灵活,在 DataBindingActivity
中使用 Kotlin 的 inline、reified 强大的特性,将类型参数实化,初始化 View 更加简洁。
继承 DataBindingActivity
class MainActivity : DataBindingActivity() {
private val mBinding: ActivityMainBinding by binding(R.layout.activity_main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding.apply {
dialog.setOnClickListener {
val msg = getString(R.string.dialog_msg)
AppDialog(
context = this@MainActivity,
message = msg,
yes = {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
}).show()
}
}
}
}
DataBindingFragment
在 Fragment 当中如何使用 Kotlin 的 inline、reified 初始化 View,可以查看 DataBindingFragment
继承自 DataBindingFragment
class FragmentTest : DataBindingFragment() {
val testViewModel: MainViewModel by viewModel()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return binding<FragmentTestBinding>(
inflater,
R.layout.fragment_test, container
).apply {
viewModel = testViewModel
testAdapter = TestAdapter()
lifecycleOwner = this@FragmentTest
}.root
}
}
JDataBinding 是基于 DataBinding 封装的 DataBindingActivity
、 DataBindingFragment
、 DataBindingDialog
、 DataBindingListAdapter
基础库。
文章中相关示例,已经上传到 GitHub 欢迎前去查看仓库 JDataBinding
。
JDataBinding
https://github.com/hi-dhl/JDataBinding
参考文献
https://github.com/skydoves/BaseRecyclerViewAdapter
推荐阅读:
最后推荐我一直在更新维护的项目和网站:
最新的 AndroidX Jetpack 相关组件的实战项目 以及 原理分析的文章
https://github.com/hi-dhl/AndroidX-Jetpack-PracticeLeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
剑指 offer:https://offer.hi-dhl.com
LeetCode:https://leetcode.hi-dhl.com最新 Android 10 源码分析系列文章
https://github.com/hi-dhl/Android10-Source-Analysis一系列国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的分析
https://github.com/hi-dhl/Technical-Article-Translation「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址
https://site.51git.cn
致力于分享一系列最新技术原创文章
长按二维码即可关注
我知道你在看哟