Android 开发必看:一文教你完全理解 DataBinding 框架(下
<data>
<variablename="test"type="String"/></data>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"
<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@{test}"/>
</LinearLayout></layout>
5. 支持 ViewStub 标签
布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"
<Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="inflateViewStub"android:text="Inflate the ViewStub"/>
<ViewStubandroid:id="@+id/view_stub"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout="@layout/view_stub"/></LinearLayout></layout>
view_stub.xml 如下:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="user"type="com.xxx.databinding.model.User"/></data>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"
<TextViewandroid:id="@+id/firstName"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}"/>
<TextViewandroid:id="@+id/lastName"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.lastName}"/></LinearLayout></layout>
代码如下:
// 设置 inflate 回调 mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener(){@Overridepublic void onInflate(ViewStub stub, View view){// 得到 Binding 实例 ViewStubBinding binding = DataBindingUtil.bind(view);User user = new User("liang", "fei");binding.setUser(user);}});
// isInflated 是 DataBinding 自动生成的方法,不是 ViewStub 的方法。可用于判断是否已 inflateif (!mBinding.viewStub.isInflated()){mBinding.viewStub.getViewStub().inflate();}
这个特别有用,因为可以判断 ViewStub 是否已经 inflate。
6. 支持双向绑定
比如 EditText,绑定了 LiveData,当 EditText 内容改变的时候,LiveData 的内容也会跟着变化。如下:
<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.editText}"/>
关键就是 @={},如此一来,我们就可以直接在 ViewModel 中取得 EditText 的内容数据了,因为 ViewModel 是不可以持有 View 实例的,所以如果没有 DataBinding,要获取 EditText 就只能通过 View 传给 ViewModel。有了 DataBinding 就方便多啦。
内置支持双向绑定的属性如下:
当然我们还可以自定义双向绑定的,后面再来说这个。
7. 支持绑定方法
上面我们的所有操作,都是绑定的变量,能绑定方法吗?当然可以。我们通过 onClick 事件来举例。
val onClick = View.OnClickListener {Log.i("MainViewModel", "commonLog - onClick: 变量")}
fun onClick(v: View) {Log.i("MainViewModel", "commonLog - onClick: 方法")}
fun onClick() {Log.i("MainViewModel", "commonLog - onClick: 没有参数,或者有参数也行,但是 xml 中也必须给它传对应的值")}
其中双冒号 :: 绑定方法的签名必须和属性要求的对象方法签名一致。这里即 OnClickListener 的 onClick 方法。同样的,如果是 onLongClick 监听,那就要求你的方法有返回 Bool 值了。
第三种是表达式的写法,我们下面会说,学过 Java8 的都知道箭头 -> 和双冒号 :: 是 Lambda 表达式。
8. 支持表达式
DataBinding 支持部分表达式,还支持 Lambda 表达式,如上的点击事件,但是建议不要过于复杂,因为 DataBinding 不支持单元测试的,报错也不是很智能。支持的运算符和关键字如下:
没有 this、super、new 和显式泛型调用。示例如下:
android:text="@{String.valueOf(index + 1)}"android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"android:transitionName='@{"image_" + id}'android:transitionName="@{image_
+ id}"
String 是无需 import 的,因为是 java.lang 包下的,但是 View 就需要了,见上面的导入 import 说明。
如果需要内嵌字符串,那么需要用单引号包裹。或者使用 `` 符号包裹,如上。
(1) 双问号,三元运算符的简化
android:text="@{user.displayName ?? user.lastName}"
displayName 不为 null,则使用 displayName,否则使用 lastName。
(2) 访问集合、数组等元素
跟访问数组一样,用 [] 括号访问,比如:
android:text="@{list[index]}"android:text="@{map[key]}"
如果是 map 类型的,甚至可以这样:map.key,就跟访问属性一样。而且会自动强转,如果你的 value 不是字符类型的,DataBinding 会强转为符合 text 属性的类型。
(3) 访问资源
比如说 strings,dimens,color 等
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"android:text="@{@string/nameFormat(firstName, lastName)}"android:text="@{@plurals/banana(bananaCount)}"
如果是自定义的 View,比如有个 set 方法参数要求是 Drawable,而 xml 中无法配置 Drawable 对象,只能是 int 型的资源地址,那么就需要自定义转换器了,转换器下面说。
9. 避免空指针异常
如下绑定:
android:text="@{String.valueOf(user.age)}"
如果 user 是空的,不会引发空指针异常,DataBinding 会根据变量的类型,自动设置默认值,比如 age 是 int,那么默认值是 0,不仅是字段,方法也一样。
10. 使用其他 View 的值
<TextViewandroid:id="@+id/tv_msg"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@{viewModel.mmMutableLiveData}"/>
<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@{tvMsg.text}"/>
tv_msg 会自动生成 tvMsg 字段,因此可以直接调用相关方法。
11. 在 Preview 窗口显示视图的默认值
注意,这里说的预览窗口的视图,而不是真实运行时的视图,我们经常需要在预览窗口预览视图,比如 TextView,有两种方法可以设置。如下:
<TextViewxmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@{viewModel.text, default=哈哈哈}"tools:text="我咧个去"/>
其中 tools 的优先级较高,同时设置则显示 tools 设置的内容。
12. 在非 UI 线程更改数据
如果绑定的是 LiveData,那么在子线程更新数据,可以使用 postValue。
如果绑定的是 ObservableXxxx 类型的,其 set 方法也可以直接在子线程中调用,DataBinding 支持在子线程中改变数据,也能响应到 UI 上,而且是线程安全的,不过不可以是集合类型的,比如 ObservableArrayMap,是线程不安全的。
因此如果是集合类型的数据,则应该统一在主线程更改数据。
13.
列表绑定
建议采用第三方绑定库:[binding-collection-adapter](
),可以很方便的实现列表绑定。
如果是自己写 Adapter,最关键的地方就在于使用 setVariable 和 executePendingBindings 方法刷新数据,这里就不展开叙述了,需要自行搜索吧。
14. 转换器
举个例子,比如你有个 float 值,需要设置给 TextView 的 text 属性,而 text 要求是字符串,很明显 float 不符合要求,那么就可以定义一个转换器,将 float 转成 String,如下:
package com.imyyq.sample
import androidx.databinding.BindingConversion
@BindingConversionfun convert(value: Float?) = value?.toString() + "我列个去"
入参是待转换的类型,出参是转换后的类型,即 Float 和 String。
那么就可以在 xml 中使用了。
val mmMutableLiveData2 = MutableLiveData(100.222f)
<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@{viewModel.mmMutableLiveData2}"/>
可能 xml 会报错,这就是 DataBinding 还不是很智能的一个体现,但是已经是可以运行了。以上只是举个例子,总之这个功能是用来转换类型的,会自动匹配入参和出参。
如果你设置如下属性,可能会感到疑惑:
android:background="@{@android:color/black}"
View 的 setBackground(Drawable background) 方法入参明明是个 Drawable 啊,而 @android:color/black 明显是个 int,int 怎么能赋值给 Drawable 呢?那是因为内置的 androidx.databinding.adapters.Converters 类已经定义了如下转换器:
@BindingConversionpublic static ColorDrawable convertColorToDrawable(int color) {return new ColorDrawable(color);}
所以不需要你再定义转换器了。
15. 绑定 View 的方法
DataBinding 库给很多常用属性都添加了支持,比如上面我们用到的 android:text,android:onClick 等,遇到不支持的属性呢?有两种解决方法,可以自定义绑定,也可以根据这个 View 的公共方法名作为属性,自定义绑定我们下面再说,这里先说下绑定 View 的公共方法。
举个例子,TextView 在 xml 中是没有 android:selected 属性的,如果你直接设置,会提示你未知属性:
如果你不使用 DataBinding,即没有 @{} 包裹该值:
android:selected="true"
那么运行都没法运行,因为没有找到这个属性。那么为什么用 DataBinding 就可以呢? 因为在 TextView 中,有 setSelected 方法,因此,只要你的 View 中,不管是自定义的 View,还是官方的 View,只要提供 setXxxx 方法,就可以有 xxxx 属性,即去掉 set 后把首字母小写,而且支持继承的方法。
比如 View 中 setAlpha 方法,TextView 继承自 View,那么就可以有 alpha 属性。不过对未定义的属性使用 android: 开头的,即使能用,也会报黄色,如上图,所以我们最好还是用 app: 作为开头,如下:
如果不是 set 开头的方法,普通的公共方法也可以的,比如 EditText 的 extendSelection(int index) 方法就不是 set 开头的,那么直接:app:extendSelection="@{2}" 即可绑定这个方法。
如果你不喜欢默认的方法名称呢?比如你有个自定义 View,有个方法是 test,如下:
@BindingMethods(value = [BindingMethod(type = CustomEditText::class,attribute = "myTest",method = "test")])class CustomEditText(context: Context?, attrs: AttributeSet?) : AppCompatEditText(context, attrs) {fun test(index: Int = 0) {Log.i("CustomEditText", "commonLog - test: $index")}}
那么即可在使用 app:test 属性,还可以直接用 myTest 属性
<com.imyyq.sample.CustomEditTextandroid:id="@+id/et"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="我是一个小蜜蜂"myTest="@{10086}"app:test="@{10086}"/>
通过 BindingMethod 注解,可以自定义属性名,并绑定指定方法。当绑定的数据变化时,会自动调用该方法。
16. 自定义绑定
上面我们讲解了如何绑定已有的方法,那么如果要绑定的方法是无参数的呢?比如 EditText 的 requestFocus() 方法,显然不能通过属性绑定,因为 xml 属性都是有值的。
假设你想对属性自定义自己的逻辑呢?比如 TextView 的 setText 方法,我想要在设置内容的时候做一个判断,把内容中所有的空格换成 = 号,当然,实现这个需求的方式有很多种,这里我们只是拿它来举例,我们该怎么做呢?
答案是通过 BindingAdapter 注解。如下:
@BindingAdapter(value = ["changeText"])fun changeText(view: TextView, text: String) {view.text = text.replace(" ", "=")}
那么 TextView 就自动拥有了 changeText 这个属性了,如下:
<TextViewandroid:id="@+id/tv_msg"android:layout_width="match_parent"android:layout_height="wrap_content"changeText="@{viewModel.mmMutableLiveData.key2 + 嘿 嘿?
}"/>
文字间的空格就都会被替换成 = 号。
注意:这里没有指定前缀,所以不能是 app:changeText,一定要和定义的一致。
还可以有多个属性:
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {}
其中 requireAll 如果为 true,那么必须同时设定以上两个属性,才能绑定成功。为 false 的话,只要有设置 value 定义的其中一个属性,都可以绑定该方法,其他未设置的 value 都是 null。所以如果是 Kotlin 的话,参数得是可空的。
还可以接收旧值,比如:
@BindingAdapter("android:paddingLeft")fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {if (oldPadding != newPadding) {view.setPadding(newPadding,
评论