DataBinding使用详解-Google官方的视图绑定
几年前,数据绑定在便已在前端界风生水起,Angular.js、React.js、vue.js等热门前端框架都具备这种能力。Android端的开源库butterknife/Anotation/dragger2等等也非常好用且广受支持;
数据绑定简单来说,就是通过某种机制,把代码中的数据和xml(UI)绑定起来,双方都能对数据进行操作,并且在数据发生变化的时候,自动刷新数据。
在2015年的谷歌IO大会上,Android UI Toolkit团队发布了DataBinding 框架, 只需要在gradle配置文件里添加短短的三行,就能用上数据绑定,不需要依赖第三方库
在app下的build.grade加上如下:
android {
....
dataBinding {
enabled = true
}
}
如果是在library中使用,那么使用使用该library的module也需要在build.gradle添加
使用数据绑定的优点
-
能有效提高开发效率,减少大量需要手动编写的胶水代码(如findViewById,setOnClickListener);
-
高性能(绝大部分的工作在编译期完成,避免运行时使用反射);
-
使用灵活(可以使用表达式在布局里进行一定的逻辑运算);
-
具有IDE支持(语法高亮、自动补全,语法错误标记)。
数据绑定的使用
布局文件的改造
使用数据绑定的布局文件以<layout>标签作为根节点,表明这是个数据绑定的布局,修改后数据绑定框架会生成对应的*Binding类
,如content_main.xml会生成ContentMainBinding类,即默认规则是:单词首字母大写,移除下划线,并在最后添加上Binding。
,之前的布局文件是以LinearLayout、RelativeLayout…几大布局为根标签,使用Databinding的布局文件如下:(@单项绑定 ; @= 双向绑定)
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
布局文件结构
上面布局文件的结构要写对为根节点,里面包含一个节点和以前布局写法的布局组件。
data节点下有节点,variable有name、type属性,name为自己定义的type类型的对象,用来在布局文件中引用;type就是你要用到的类啦,比如上面写的JavaBean类或者点击事件的android.view.View.OnClickListener接口类。data节点下还有一个节点,写法如下:
<data>
<import type="com.test.qby.newtestapplication.model.TestModel"/>
<variable
name="test"
type="TestModel" />
</data>
此时variable属性type的值为import属性type的类名TestModel,这就可能会出现如果项目中在不同包名下有相同类名的类需要同时引用怎么办?google也给了解决办法:import还有一个alias属性,给导入的类起别名,比如:
<data>
<import type="com.test.qby.newtestapplication.model.TestModel" alias="ModelTest"/>
<import type="com.test.qby.newtestapplication.viewmodel.TestModel"/>
</data>
不想设置variable的话,引用的时候直接用@{ModelTest.getName()}和@{TestModel.getName()},但是需要getName()为static方法,这样就可以解决类名冲突了。
布局文件中对data数据的引用:
android:text="@{@string/test+test.name}"
数据的声明和辅助类导入
在<layout>标签内部添加<data>标签,即可声明数据。给<data>标签添加class属性可以改变生成的*Binding类的名字,如使用<data class="ContentMain">将其改为ContentMain。
数据标签内部通过<variable>标签声明变量,通过<import>标签导入辅助类,为了避免同名冲突,可以使用alias属性指定一个别名。
代码调用
页面中调用方式也有几种
//1
ActivityMainTestBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_test);
//2
View inflate = LayoutInflater.from(this).inflate(R.layout.activity_main_test, null);
ActivityMainTestBinding bind = DataBindingUtil.bind(inflate);
//3
ActivityMainTestBinding inflate = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.activity_main_test, null, false);
根据情况自己选择方式就行,如:
在activity下:private void initView() {binding = DataBindingUtil.setContentView(this, R.layout.activity_map);
}
在fragment下:
databinding写法:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.activity_connection_pager,
container, false);
onClick();
initData();
return binding.getRoot();
}
原写法:
@Override
protected View initView() {
view = LayoutInflater.from(getContext()).inflate(R.layout.pager_map, null, false);
bindViews();
init();
return view;
}
数据模型
虽然数据绑定支持的POJO(Pure Old Java Object,普通Java类,指仅具有一部分getter/setter方法的类),但对POJO对象的数据更新并不会同步更新UI。为了实现自动更新,可以选择:
-
继承自BaseObservable,给getter加上@Bindable注解,并在setter中实现域的变动通知。
-
如果数据类无法继承BaseObservable,变动通知可以用PropertyChangeRegistry来实现。
-
最后一种是使用Observable域,对数据存取通过ObservableField<T>的get、set方法调用实现。ObservableField<T>是泛型类,对于基础类型,有对应的ObservableInt、ObservableLong、ObservableShort等可供使用;另外对于容器,每次只会更新其中的一个项,而不是整个更新,因此还有对应的ObservableArrayList、ObservableArrayMap可供使用。
第一种:JavaBean继承BaseObservable
public class TestModel extends BaseObservable{
private String name;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
JavaBean继承BaseObservable;getter方法添加@Bindable注解;settter方法内部添加通知notifyPropertyChanged;
第二种:JavaBean中字段改为ObservableField
Databinding提供的类包括:ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable。
public class TestModel {
public ObservableField<String> name = new ObservableField<>();
public ObservableBoolean name = new ObservableBoolean();
}
别忘了字段修饰符要public,调用直接使用set()
TestModel testModel = new TestModel();
testModel.name.set("这是测试文字");
testModel.isTrue.set(true);
布局中引用方式与其他的没区别,在代码中获取值就直接调用get()
String name = testModel.name.get();
boolean isTrue = testModel.isTrue.get();
这种方法虽然看着没有getter/setter方法,但是它是在内部实现过了。
第三种:不使用JavaBean,创建Observable Clollections
这种方式是为了不创建Javabean,而使用动态数据结构来更新UI。Databinding提供了ObservableArrayMap,ObservableArrayList两个类来实现。
使用代码动态创建
ObservableArrayList<Object> observableList = new ObservableArrayList<>();
observableList.add("databindingList");
observableList.add(12);
binding.setListIndex(1);
binding.setObservableList(observableList);
ObservableArrayMap<String,Object> observableMap = new ObservableArrayMap<>();
observableMap.put("hash1", "databindingMap");
observableMap.put("hash2", 16);
observableMap.put("hash3", "显示");
binding.setKey("hash3");
binding.setObservableMap(observableMap);
使用方式跟使用ArrayList、HashMap没什么区别
<variable
name="list"
type="android.databinding.ObservableArrayList<Object>" />
<variable
name="map"
type="android.databinding.ObservableArrayMap<String,Object>" />
<variable
name="listIndex"
type="int" />
<variable
name="key"
type="String" />
</data><LinearLayout
android:orientation="vertical"
style="@style/MathchMathch">
<TextView
style="@style/MatchWrap"
android:text="@{String.valueOf(list[listIndex])}"/>
<TextView
style="@style/MatchWrap"
android:text="@{String.valueOf(map[key])}"/>
</LinearLayout>
-
常见问题
1. xxx.databinding.xxxbinding类文件不存在
这个就很简单了,如果是使用语法问题,log会有相应的记录,可以略过log中前面众多databinding类文件不存在的提示,只看最后俩三行就可以直接明了的找到原因。
如果,你在最后俩三行也没找到明确的提示你错误原因,(什么是明确?就是你不知道你的代码哪一行哪个地方出现错误),那么,你应该不久前手抖了一下,删除或者增加了布局文件某一个地方,造成xml有语法错误,但是坑爹的是使用layout包裹后的布局文件根本不会提示你你的xml布局有问题。
2.DataBinding对象无法 '.' 出布局中新增加的View的id
这样就是找不到新增加的view,即使你build也依然找不到这个id,首先确保自己id写正确的,布局也是正确的,然后仅需重启AndroidStudio即可貌似有时binding类不会随着布局文件实时更新.
使用推荐:
-
使用数据绑定,实现了数据和表现的分离,结合响应式编程框架RxJava、RxAndroid,编码体验和效率能还能进一步提高。
-
由于数据绑定实现了数据和表现的分离,由Data Binding框架对接UI,可以通过自定义Adapter,干预某些属性的属性读取和设置,比如拦截图片资源的加载(换肤)、动态替换字符(翻译)等功能。
-
方便UI复用,Android上进行UI组件化的时候,可以在布局的层次上进行复用,业务无关的UI逻辑也能一起打包,同时保持对外接口(数据模型)简单,学习接入成本更小。
-
DataBinding在xml提供了丰富的操作符,但是由于Android studio天生的xml语法检查的贫弱,xml布局中的表达式逻辑错误,不能准确定位,导致debug难度增加,事实上一些BindingAdapter的错误在build的时候也会被提示xml错误。
本文参考:
https://www.jianshu.com/p/ba4982be30f8;
https://blog.csdn.net/qby_nianjun/article/details/79198166