关于数据绑定库,很多人的评价是难用,出问题难以排查,其实也就是的,哈哈,不过该用还得用,有问题解决问题。
概述
数据绑定库是一种支持库,借助该库,你可以使用声明性格式(而非程序化地)将布局中地界面组件绑定到应用中地数据源。
简单来说就是布局中的值再也不用使用通过findViewById找到这个组件再调用组件属性赋值了,可以直接绑定到某个数据源。
编译环境
哪个模块需要使用数据绑定功能,则在这个module的build.gradle中添加:
android {
...
dataBinding {
enabled = true
}
}
复制代码
同时需要添加apt或者kapt:
plugins {
id 'kotlin-kapt'
}
复制代码
注意这里是按moudule来配置的,而不是在common模块里配置就可以了,是个坑的地方。
其他内容
- 绑定表达式中使用default属性,使用default可以对比如text的值设置一个默认值:
<com.wayeal.common.view.ClearEditTextMain
android:id="@+id/textUserId"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="89dp"
android:drawablePadding="8dp"
android:allowUndo="false"
android:drawableLeft="@mipmap/wy_cloud_user"
android:ems="10"
android:hint="@string/user_id"
android:background="@null"
android:inputType="textPersonName"
android:textSize="15sp"
android:textColor="@color/commonTextColor"
android:textColorHint="@color/commonTextHint"
android:text="@={viewModel.userName,default = 张三}"
/>
复制代码
比如这里的的EditText的值会根据viewModel变化,但是会有个默认值default。
布局和绑定表达式
设置完环境后,系统会为每个布局文件生成一个绑定类,这个生成的过程是系统做的,我们无法干预,这个绑定类包含从布局属性到布局视图的所有绑定,并且知道如何为绑定表达式指定值。
然后就是binding在代码里获取以及使用,一直是模糊不清的,这里总结几种方法:
- 当在activity中使用,可以在onCreate中得到binding,同时可以调用setContentView方法,这个也是Activity在onCreate中必须要执行的方法,具体代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.user = User("Test", "User")
}
复制代码
还有就是baseVMActivity也是这种方法:
abstract class BaseVMActivity<VM : BaseViewModel>(useDataBinding: Boolean = false) : AppCompatActivity() {
private val _useBinding = useDataBinding
protected lateinit var mBinding: ViewDataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startObserve()
if (_useBinding) {
mBinding = DataBindingUtil.setContentView(this, getLayoutResId())
mBinding.lifecycleOwner = this
} else setContentView(getLayoutResId())
initView()
initData()
}
open fun getLayoutResId(): Int = 0
abstract fun initView()
abstract fun initData()
abstract fun startObserve()
}
复制代码
- 当在Fragment、ListView或者RecyclerView适配器中使用数据绑定,那肯定也需要得到binding,这里以Fragment为例,是在onCreateView方法里获取,通过DataBindingUtil的inflate方法来获取,直接看BaseVMFragment中的写法:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return if (_useBinding) {
mBinding = DataBindingUtil.inflate(inflater, getLayoutResId(), container, false)
mBinding.root
} else
inflater.inflate(getLayoutResId(), container, false)
}
复制代码
然后就是sunflower中的写法:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentGardenBinding.inflate(inflater, container, false)
val adapter = GardenPlantingAdapter()
binding.gardenList.adapter = adapter
subscribeUi(adapter, binding)
return binding.root
}
复制代码
其他的写法,我们用到的时候再说。
表达式语言
表达式语言这个是什么东西呢,就是在xml代码里通过@{}取值的表达式,这里的表达式能否丰富直接影响着这个控件还要不要额外处理,比如我一个控件的值需要是根据某个viewModel里的值进行复杂的运算,那如果表达式语言不支持,就完了,所以看一下常用的有哪些。
- 首先是一些常见的运算符和关键字,比如算术运算符,逻辑运算符,三元运算符等等,直接看代码:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
复制代码
这里万变不离其宗,都是取得是@{}括号中的值。
- Null合并运算符,这个特别方便,如果左边不是null,则选择左边运算数,如果左边为null,则选择右边运算数,这个完全就是三元运算符的简写:
android:text="@{user.displayName ?? user.lastName}"
复制代码
等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
复制代码
- 属性引用,顾名思义就是表达式可以使用引用对象的属性,也正是因为这个,可以在ViewModel中定义引用类型,在xml里进行引用:
android:text="@{user.lastName}"
复制代码
-
避免出现Null指针异常,这个就比较好了,也就是上面说的引用时,当某个引用是null时,不会导致空指针异常,比如@{user.name}中,如果user为null,那默认值会是null。
-
使用集合,集合在平时使用中用到的很多,比如List、Map等,那肯定在XML中的data部分也需要使用,但是这里使用的时候需要注意,首先是要import全名,然后使用泛型时的<>需要转义,直接看个代码:
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
复制代码
比如上面中先在data中import出Map,然后在泛型中要把<转义。
- 字符串字面量问题,为什么要说这个问题呢,在之前表达式中是以双引号””中@{}来取值,但是字符串字面量也是通过””来获取,所以这里就会出问题,解决的方案也非常暴力,直接使用单引号”,要不外面双引号里面字符串字面量用单引号,或者外面用单引号里面字符串字面量用双引号,直接看代码:
android:text='@{map["firstName"]}'
复制代码
android:text="@{map[`firstName`]}"
复制代码