Databinding#
本文主要讲了Databinding以下用法:Activity用法、Fragment用法、Recycleview用法、布局引用、更新字段、事件处理等。
参考Android DataBinding 从入门到进阶 - 掘金 (juejin.cn)
用途#
声明式的将视图与数据绑定,而程序式的调用视图组件属性去设置
如程序式的做法
1 | TextView textView = findViewById(R.id.sample_text); |
声明式的做法
1 | <TextView |
常见问题#
问题1:使用数据绑定类还是视图绑定类?
解决:
- 如果只是取代findViewById,可以使用视图绑定类
问题2:
1 | If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static. |
解决:
@BindingAdapter
注解声明的方法必须是静态的
问题3:databinding能在子线程直接设置对象的值吗?
写demo验证:起子线程调用databinding设置变量值
button点击事件代码如下,经过验证,每点一下,界面是可以实时刷新的。
1 | public void baseObservableInThread(View view) { |
官方文档专门描述了:异步设置数据的前提是,数据源不能是集合。——《生成的绑定类-后台线程》
问题4:能替代handler通知主线程更新ui的作用吗?
答:确实不需要handler通知主线程刷新UI了。
问题5:那还需要handler吗?
答:handler仍然可用于多线程通信
问题6:databinding可观察对象发生更改时,界面是在当前帧显示变化还是下一帧显示变化?
答:当可变或可观察对象发生更改时,绑定会按照计划在下一帧之前发生更改。但有时必须立即执行绑定。要强制执行,请使用 executePendingBindings()
方法。
问题7:双向绑定可能会引入无限循环
使用双向数据绑定时,请注意不要引入无限循环。当用户更改特性时,系统会调用使用 @InverseBindingAdapter
注释的方法,并且该值将分配给后备属性。继而调用使用 @BindingAdapter
注释的方法,从而触发对使用 @InverseBindingAdapter
注释的方法的另一个调用,依此类推。
解决:通过比较使用 @BindingAdapter
注释的方法中的新值和旧值,可以打破可能出现的无限循环。
几个重要的类#
DataBindingUtil
- 为Activity、Fragment、Recyclerview设置布局
ViewDataBinding
- view级别的DataBinding
优势#
维护简单:不用写视图组件调用findViewById
减少内存泄漏
避免Null指针异常,如果user.name值为空,会默认分配null值
如
android:text="@{exampleText.text}"
text为空,实际效果为android:text="null"
支持viewstub
布局嵌套:A.xml通过include标签包含了一个B.xml,A可以通过命名 空间的形式将变量传递给B布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
缺点是不支持merge
数据类型#
DataBindingUtil | |
基本用法#
常见API#
DataBindingUtil类 | 用途 |
---|---|
inflate | 返回一个数据绑定类对象 |
setContentView | 为组件设置视图;返回一个数据绑定类对象 |
bind | |
getBinding | 返回一个数据绑定类对象 |
开启数据绑定类#
需要几个前置条件
Android 4.0(API 级别 14)及以上
gralde插件版本大于1.5
1 | android { |
初始化数据绑定类#
如何根据xml文件,初始化生成对应的数据绑定类呢?答案是有2种初始化方式
第一种直接使用list_item.xml自动生成的ListItemBinding绑定类,该类的静态方法
第二种使用DataBindingUtil.inflate将list_item.xml布局初始化为ListItemBinding绑定类
1 | // 方式1 item_test_test.xml文件自动生成ItemTestTEstBinding.java |
重命名绑定类名称#
默认情况下,数据绑定类的名称由布局文件名称生成,生成步骤为
- 大写字母开头
- 移除下划线
- 每个词汇字母开头大小
可以调整数据绑定类的名称,修改方式为在data标签里定义class属性
1 | <data class="ContactItem"> |
Activity入门#
第一步编写xml
新建一个xml,鼠标放在根布局标签上按住ALt+回车键,可以看到Conver to data binding layout
按钮,点击后自动将当前布局转换为数据绑定类布局文件
下面是转换后的布局文件
1 | <data> |
第二步activity绑定xml
1 | setContentView(R.layout.activity_databind); |
第三步为xml赋值
1 | User user = new User(); |
第四步更新user类,观察xml是否改变
1 | user.setUserName("jordan" + aLong + "号"); |
观察界面是否变化
Recycleview入门#
第一步:编写Recycleview.Adapter
1.1 编写ViewHolder,增加ViewDataBinding相关接口
1 | class MyViewHolder extends RecyclerView.ViewHolder { |
1.2 重写onCreateViewHolder,调用Databinding相关类,初始化ViewDatabinding,初始化ViewHolder,返回给调用者
1 |
|
1.3 重写onBindVIewHolder,将数据集,通过databind#setVariable塞给子项item
1 |
|
1.4 完成俩其他必备的方法
1 |
|
默认值#
1 | android:text="@{userInfo.name,default=defaultValue}" |
动态设置指定ID组件#
1 | activityMain2Binding.tvUserName.setText("leavesC"); |
布局引用#
什么是布局引用?
在xml
的一个组件里,直接引用另外一个组件的属性,比如下面的例子,editext
的id
为ed_test_text
;我们想做的是编辑该组件时实时刷新TextView
,在数据绑定类提供的功能里,我们可以直接驼峰式写出组件id的名称,名称后跟上其可用的属性.
就像下面的例子ed_test_text
的写法为edTestText
,布局引用的写法为@{edTestText.text}
1 | <EditText |
BaseObservable更新指定字段#
方式1:先继承BaseObservable类,采用notifyPropertyChanged的方式,传入BR值
1 | build\generated\source\apt\debug\com\shaunsheep\mvvm\databinding |
方式2:ObservableField更新
官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型
1 | public class UserObservable { |
ObservableField更新指定数据#
定义:指可观察对象将其数据变化告知其他对象
用途:当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新
用法:使用ObservableField封装指定数据,
1 | /** |
在xml
中绑定该数据,先在data
标签中声明variable
变量user2
,后再视图组件中使用user2
变量,将该变量绑定到xml中。
1 | <variable |
这样,当我们调用数据绑定类设置user2的值时,
1 | UserObservable userObservable = new UserObservable(";", ";", 0L); |
BaseObservable更新全量字段#
调用notifyChange
1 | build\generated\source\apt\debug\com\shaunsheep\mvvm\databinding |
data标签支持的标签#
支持的属性 | |
---|---|
import | type |
variable | name、type、alis |
转义字符#
属性名称 | < 代替< |
---|---|
List<String> |
List<String> |
SparseArray |
SparseArray<String> |
Map<String,String> |
Map<String, String> |
int | |
String |
可用表达式#
写法 | 解释 |
---|---|
android:text=’@{“”}’ | 空串-外侧单引号,内侧双引号 |
android:text=”@{``}” | 空串-外侧爽引号,内侧反单引号 |
@{goods.details} | 字符串赋值 |
@{String.valueOf(goods.price)} | 类型转换 |
@{()->goodsHandler.changeGoodsName()} | 点击事件 |
@{list[index],default=xx} | 集合取值 |
@{map[key],default=yy} | map取值 |
@{map.key} | map取值 |
@{user.male ? View.VISIBLE : View.GONE}” | 视图可见 |
@{set.contains(“xxx”)?”xxx”:key} | 运算符示例 |
@{flag ? @dimen/paddingBig:@dimen/paddingSmall} | 资源文件索引,只能通过表达式赋值,不能直接@drawwable/resoursename |
@{user.name != null ? user.name : user.password} | 空合并运算符,取第一个不为null的值 |
xml布局
1 | <?xml version="1.0" encoding="utf-8"?> |
ui层
1 | public class Main3Activity extends AppCompatActivity { |
事件处理#
根据官网介绍,事件处理总共有两种写法,一种是自定义方法引用,一种是监听器绑定
自定义方法引用#
共有3步
- 自定义类和方法,方法必须为
public
,入参必须带View - 在布局文件中import导入该类
- 在
button
的onclick
属性里传入类名::方法名
,注意用两个冒号隔开 - 在Activity中创建类的实例,传入数据绑定类
定义一个类MyHandlers,里面定义点击事件的回调方法onClickFriend
1 | public class ButtonClickHandler { |
xml
中绑定类MyHandlers
的回调方法
data
标签中声明类型MyHandlers的变量handler
view
的onClick
属性绑定该类的属性android:onClick="@{handlers::onClickFriend}
1 |
|
1 | activityDatabindBinding.setBtnhandler(new ButtonClickHandler()); |
自定义监听器#
分为3步
- 创建类和方法,方法入参任意写,比如User对象
xml
中导入该类,导入User类button
的onclick
属性传入@{()->类名.方法名(user)}
Activity
中为Databinding
设置类的实例化对象
!>注意入参要和方法声明保持一致,这个例子为User
方法和类的声明
1 | public class ButtonClickHandler { |
导入
1 | <variable |
传入属性
1 | <Button |
设置类的实例化对象
1 | activityDatabindBinding.setMyHandlers(new ClickPresenter()); |
双向绑定view#
@符号后多了一个符号=
写法 | |
---|---|
<EditText ··· android:text=”@={goods.name}” /> | 双向同步 |
自定义特性的双向绑定#
问:有什么用?
答:实现双向数据绑定——数据源修改,视图变化;视图变化,数据源也发生变化;
需要使用@BindingAdapter
、@InverseBindingAdapter
两个注解。
BindingAdapter
用于数据绑定类调用该方法,为view设置成员变量的属性值、为view设置InverseBindingListener
子类实现InverseBindingAdapter
用于让数据绑定类获得该方法的返回值,达到getValue
的效果
例如我们要为ScrollView
制作上拉到底部,显示加载更多布局,在编码前,我们进行如下设计
- 我们在
ScrollView
子view里定义一个类型为boolean的成员变量footerRefreshing
,用于控制加载更多布局的显示和隐藏 - 我们在
ScrollView
子View
里提供三个方法
- 方法1通过
@BindingAdapte
声明数据绑定类如何设置view的属性值
1 |
|
- 通过
@InverseBindingAdapter
声明数据绑定类如何获取view的属性值
1 |
|
- 通过
@BindingAdapte
声明监听器,当view的成员变量footerRefreshing
发生变化的时候,会返回一个InverseBindingListener
子类实现,该类有一个onChange
方法,用于告知数据绑定类,成员变量发生变化
1 |
|
- 在合适的时机,如监听到滑动至
ScrollView
底部的事件,显示加载更多布局;延时指定时间后,隐藏加载更多布局
1 | public void startRefreshing() { |
下拉刷新加载更多Demo源码地址
Demo时序图
自定义特性的用法总结#
假设存在类型为A、属性名为a的变量
@BindingAdapter声明2个公有静态类方法,传入的value值都为A,方法入参不同,前者用于数据绑定类为View设置属性a、后者为用于数据绑定类为View设置属性监听
1 |
|
@InverseBindingAdapter声明1个静态公有类方法,传入View,用于数据绑定类获取View的成员属性值a
1 |
|
基础运算符#
DataBinding 支持在布局文件中使用以下运算符、表达式和关键字
- 算术 + - / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比较 == > < >= <=
- Instanceof
- Grouping ()
- character, String, numeric, null
- Cast
- 方法调用
- Field 访问
- Array 访问 []
- 三元 ?:
目前不支持以下操作
- this
- super
- new
- 显示泛型调用
注解#
@BindingAdapter | 事件监听、属性赋值、类型转换、埋点业务、防重按等切片编程 |
@Bindable | 更新私有属性 |
@InverseBindingAdapter | 参考这篇文章 |
@InverseBindingMethod | |
@InverseMethod |
绑定适配器#
android Jetpack DataBinding - 注解篇(BindingAdapter) - summer_xx - 博客园 (cnblogs.com)
定义#
BindingAdapter :绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解,允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展,这个属性可以是自定义属性,也可以是原生属性。这个扩展行为可以是简单的ViewModel属性与控件赋值绑定,也可以是关联某个控件属性的额外操作,例如在设置属性之前进行值域检查,或类型转换,或者统一处理一些事情。
关键词:扩展xml属性的行为
假设有2个变量,这个两个变量作用于2个view,根据这2个变量我们可以设置ImageView的资源文件,资源文件来自于网络、磁盘等
很明显我们会遇到困难:通过databind.setImagePath、databind.setImagePath2,无法实现这个目的
1 | <variable |
src是默认的控件属性,而src需要使用一个资源ID,来完成赋值,原生的ImageView并不支持网络地址来获取设置图片,此时就可以通过 BindingAdapter 来扩展行为,使其具备使用一个网络地址,并且从网络下载图片并显示的能力。
这一段代码就实现了对android:src
属性的扩展,有以下3个要点:
调用
databind.setImagePath
、databind.setImagePath2
它会触发
android:src="@{imgpath}"
、android:src="@{imgpath2}"
此时会寻找匹配src的注解方法,通过注解上的声明
@BindingAdapter("android:src")
,自动找到了静态函数setSrc
,
1 |
|
定义静态方法#
绑定方法的位置:绑定适配的方法可以随意放在任何 类中,你可以自定义一个类,也可以放在Activity里,APT会在构建时扫描全局代码
多属性写法:
1 |
|
requireAll
用于校验数量一致性:注解声明的属性标签数量必须等于方法入参数量
默认是true:表示函数入参必须包括所有注解声明的xml属性标签,缺一不可;
如果设置false,表示不需要关乎所有的xml属性标签
1 | public class ViewAttrAdapter { |
当requireAll = true ,注解处声明4个属性,入参参数必须是4个
1 |
|
设置多个属性#
useAdapter设置多个属性值;静态注解方法里value参数传入标签数组,数组写法为大括号包裹,逗号分割开,每个属性表情为双引号包裹
1 | public void useAdapter(View view) { |
布局文件参考如下
可以看到2个ImageView有两个属性
- android:paddingLeft=”@{paddingLeft}”
- android:src=”@{imgpath}”
1 | <variable |
判断新值和旧值#
静态方法为一个变量可以接收2个参数,第一个参数是指旧值,第二个参数是指新值
1 |
|
接收多个新值和旧值#
同时获取旧值和新值的方法应该先为所有属性声明所有旧值,然后再按顺序声明所有属性的新值
1 |
|
常见问题#
问题1:绑定适配器的匹配规则是什么?
解决:输入什么值,就跟什么值进行模糊匹配:如src可以与android:src
和src
匹配
1 | // 这个限定使用时一定是android:src才能匹配 |
问题2:old values should be followed by new values
可能出现于2种场合
- 注解的value参数,设置了错误的属性标签数量
- 有多个属性标签,且出现了新值和老值
解决:
情况1:错误写法@BindingAdapter({"created_by,created_at"}
正确写法@BindingAdapter({"created_by","created_at"}
,双引号包裹单个属性标签,用逗号分割多个属性标签
情况2:存在多个新值老值得参数情况,顺序为:oldValueA,oldValueB,newValueA,newValueB,先按顺序写老值,后按顺序传新值;正确写法为接收多个新值和旧值