用了DataBinding 快一年了,在这个过程中,经常会忘记一些DataBinding 最本质,最基本的东西,不知道大家有木有这种感觉,就是当一个东西用久了,用熟练了,突然某一天,被问到这个东西的原理、构造,你会懵住,因为我们已经习惯了它的使用方法。特此,写下这篇文章,方便后期温故而知新嘛!
我是谁
用 Google 自己的话说就是
DataBinding 框架是一个 将 数据 以陈述式(直截了当)而不是程序式的绑定到 UI 控件上的框架。
DataBinding 是谷歌推出的 实现MVVM 模式的一种框架,将数据绑定到UI上,不单单是 通过改变数据来更新UI,同时UI改变也可以更新数据,达到View 和 Model 解耦的目的。
配置
build.gradle 文件中加上如下配置:
dataBinding {
enabled = true
}
认识标签
- layout xml布局的根节点
- data 包含引用数据的根节点
- variable 需要引入的数据部分(type:需要导入的类限定名 name:给导入的类起的名字)
- import 直接导入需要的类 (type:需要导入的类限定名 [alias]:如果有多个导入的类名是一样的,需要起个别名加以区分)
简单数据绑定
People Bean类
新建了两个属性:name 和 age,getter 和 setter 方法。
public class People {
private String name;
private String age;
public People(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
activity_main.xml
新建了 两个 TextView 控件 , 通过 @{people.xxx} 给TextView 的 android:text 属性赋值。这边在<data> 里引用了 People类,两个控件分别赋予了 people.name 和 people.age 属性,DataBinding 会映射到相应的 getterXxx() 方法上。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.example.myapplication.R" />
<import type="android.view.View" />
<import
alias="xxx"
type="android.view.View" />
<variable
name="people"
type="com.example.myapplication.People" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.name}"
android:textColor="@android:color/holo_orange_dark"
android:textSize="25sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.age}"
android:textColor="@android:color/holo_red_light"
android:textSize="25sp" />
</LinearLayout>
</layout>
MainActivity
当你在 layout xml 中使用DataBinding 后,会自动生成一个 xxxBinding 类,这个类名是 根据你的 layout xml 命名来的,比如 activity_main.xml,那么就会生成 ActivityMainBinding。
去除了之前的 setContentView() 方法,取而代之的是 DataBindingUtil.setContentView() 。接着就给我们在 <data> 里引入的类设置实例。这里有两种方法:
- activityMainBinding.setPeople(people);
- activityMainBinding.setVariable(BR.people,people);
setPeople() 和 BR.people 中的 people 是我们在 <variable> 里设置的 “name” 值。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
People people = new People("zhang san", "22岁");
activityMainBinding.setPeople(people);
//activityMainBinding.setVariable(BR.people,people);
}
}
如果不想让框架根据xml 名字来自动生成 xxxBinding 类,或者有时候 xml 名字很长,倒是生成的 xxxBinding类 名字也很长,咋整?
可以使用 <data class="xxxBinding"> 来自定义 xxxBinding 类名。
<data class="CustomDataBinding">
<import type="com.example.myapplication.R" />
<import type="android.view.View" />
<variable
name="people"
type="com.example.myapplication.People" />
</data>
有了 DataBinding 后,
findViewById()也可以直接省略,替换成 xxxBinding.控件name的驼峰式
<TextView
android:id="@+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.name}"
android:textColor="@android:color/holo_orange_dark"
android:textSize="25sp" />
activityMainBinding.nameTv.setText("我是 李四");
讲到这里,大家有木有想过一个问题?
如何更新数据,总不能是每次都新建一个 people 实例再设置上去吧?最好就是当我们 更新了 people 中的 name 或 age 属性,UI 也跟着改变。
单向数据绑定
有三种方式可以让数据 驱动 UI:
- BaseObservable
- ObservableField
- ObservableCollection
BaseObservable
public class People extends BaseObservable {
private String name;
// 如果是private,直接再变量上加上注解: @Bindable
private String age;
public People(String name, String age) {
this.name = name;
this.age = age;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyChange();
}
@Bindable
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
notifyPropertyChanged(BR.age);
}
}
activityMainBinding.updateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
people.setAge("18");
people.setName("李四");
}
});
让Bean 类 继承 Baseobservable ,表明自己是可被观察的。
如果属性是 private,则在 getXxx()方法上加上注解 @Bindable ,如果是public的,则直接在属性上方加上注解。
为了能够在设置值时,通知UI更新数据,需要在setXxx()方法里加上 notifyChange() 或者 notifyPropertyChange(BR.xxx),注意两者是有区别的,前者是 更新该bean 类 所有的属性,后者是 更新指定的属性。
多说一句:除了有 notifyChange() 和 notifyPropertyChange(BR.xxx),还可以使用 addOnPropertyChangedCallback() 来监听 任何一个属性改变时的回调,debug的时候非常方便。
看到这里,有的杠精就可是举手了,并不是每次都要用到Bean 类的,或者不想把所有可观察属性封装到一个Bean类,咋整?对于一些零散的或者更精细化的 变量,可以是用 ObservableField。
ObservableField<T>
ObservableField 不像是之前写 Bean类那么麻烦,自己已经封装好了 set() 和 get() ,使用起来 简洁高效。除了泛型以外,google 还为我们封装了一系列的具体可观察类:
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableShort
- ObservableInt
- ObservableLong
- ObservableFloat
- ObservableDouble
- ObservableParcelable
新建一个存放 ObservableField 的数据类
public class DataObject {
public ObservableField<String> name = new ObservableField<>();
public ObservableField<String> age = new ObservableField<>();
}
这边和之前一样,导入类,再引用类中的属性
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.example.myapplication.R" />
<import type="android.view.View" />
<variable
name="dataObject"
type="com.example.myapplication.DataObject" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{dataObject.name}"
android:textColor="@android:color/holo_orange_dark"
android:textSize="25sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{dataObject.age}"
android:textColor="@android:color/holo_red_light"
android:textSize="25sp" />
<Button
android:id="@+id/update_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改变 name 和 age" />
</LinearLayout>
</layout>
final DataObject dataObject = new DataObject();
activityMainBinding.setVariable(BR.dataObject, dataObject);
activityMainBinding.updateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dataObject.age.set("18");
dataObject.name.set("李四");
}
});
在更新数据时 直接调用了 ObservableField 的 get()方法,这样UI 会做出响应。
不知道大家有木有发现,一开始没有设置值时,控件上的内容没有显示出来,也没有报空指针异常,这就是DataBinding 的奇妙之处。
ObservableCollection
- ObservableList
- ObservableMap
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="androidx.databinding.ObservableList" />
<variable
name="nameIndex"
type="int" />
<variable
name="ageIndex"
type="int" />
<variable
name="list"
type="ObservableList<String>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[nameIndex]}"
android:textColor="@android:color/holo_orange_dark"
android:textSize="25sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[ageIndex]}"
android:textColor="@android:color/holo_red_light"
android:textSize="25sp" />
</LinearLayout>
</layout>
ObservableList<String> observableList = new ObservableArrayList<>();
observableList.add("zhang san");
observableList.add("19岁");
activityMainBinding.setNameIndex(0);
activityMainBinding.setAgeIndex(1);
activityMainBinding.setList(observableList);
这个代码比较简单,map和list差不多,这里就不多赘述了,大家可以仿照这写。
双向数据绑定
什么时双向数据绑定?就是 数据改变会驱动UI更新,相反,UI改变 也会去更新数据。
与之前 @{xxx}相比,双向数据绑定就多了一个 “=”,形成了 @={xxx}。
public class DataObject {
public ObservableField<String> content = new ObservableField<>();
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="dataObject"
type="com.example.myapplication.DataObject" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="50dp"
android:gravity="top"
android:text="@={dataObject.content}"
android:textSize="25sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{dataObject.content}"
android:textColor="@android:color/holo_red_light"
android:textSize="15sp" />
</LinearLayout>
</layout>
DataObject dataObject = new DataObject();
dataObject.content.set("这是设置上去的值");
activityMainBinding.setDataObject(dataObject);
运算符
在了解完 DataBinding 的数据绑定之后,很多小伙伴都会有疑问,能能在 xml 中对绑定的数据进行一些简单的 运算或逻辑判断呢?其实我们想到的,谷歌早就帮我解决了这个问题。
支持的运算符:
- 算术 + - / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比较 == > < >= <=
- Instanceof
- Grouping ()
- character, String, numeric, null
- Cast
- 方法调用
- Field 访问
- Array 访问 []
- 三元 ?:
不支持的运算符:
thissupernew显示泛型调用
空合并运算符(??)
传统写法:android:text="@{people.name == null ? people.age:people.name}"
新的写法:android:text="@{people.name == null ?? people.age}"
如果 ??的左侧不为空就返回左侧表达式 ,否则返回右侧表达式
属性引用的格式统一
一般我们会 引用 POJO,Java Bean 中的属性 ,还有 ObservableField 变量,但是在XML 中 引用它们时,格式是统一的:
android:text="@{dataObject.content}"
主动规避 空指针 异常
DataBinding 生成的代码 会 自动帮我们避免空指针异常,如果 “dataObject” 是空的,那么“dataObject.content” 默认赋值是空,而不会抛出一个空指针。
Resource 资源文件的引用
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.age}"
android:textColor="@android:color/holo_red_light"
android:textSize="@{TextUtils.isEmpty(people.age)?@dimen/textsize15:@dimen/textsize25}" />
可以为格式化string
和plurals
提供参数
这一块很多 博客都没有讲明白,都是照搬谷歌官网的例子,导致很多读者看到这个也有点懵,作为新时代的青年博主,有责任为大家解惑
<resources>
<string name="app_name">Data Binding</string>
<string name="people_Info">名字%1$s 年龄%2$s</string>
</resources>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/people_Info(people.name, people.age)}"
android:textColor="@android:color/holo_red_light" />
注意:如果格式化的字符串里有多个参数,那么在表达式里就需要 传入所有参数
其他资源文件的正确引用
前三个 都不知道在 xml 里怎么用,先标记一下。
Include & View stubs
在写布局文件时,为了灵活性,我们肯定会用到 Include 和 View Stubs,同样,DataBinding 也支持这些,接下来让我们看看怎么用的?
Include
include_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="peopleInfo"
type="com.example.myapplication.People" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{peopleInfo.age}" />
</LinearLayout>
</layout>
activity_main.xml
<?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="peopleInfo"
type="com.example.myapplication.People" />
<import type="android.text.TextUtils" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{peopleInfo.name}"
android:textColor="@android:color/holo_red_light" />
<include
layout="@layout/include_layout"
bind:peopleInfo="@{peopleInfo}" />
</LinearLayout>
</layout>
被绑定的布局和以前没什么差别,关键就在 主布局上面,在 <include> 中 使用 “bind:xxx” 来将引用的变量进行值传递。现在两个页面上用到的 <variable > 中 “name” 属性名称 是一样的,那么现在的问题是:这两个 “name" 可以不一样吗? 大家可以试一下,其实是可以的,只要 "bind:xxx" 的 中的属性名称 和 被绑定中 <variable> 中的 "name" 属性值一样即可。
View stubs
stubs_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="peopleInfo"
type="com.example.myapplication.People" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{peopleInfo.age}"
android:textSize="25sp" />
</LinearLayout>
</layout>
activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="peopleInfo"
type="com.example.myapplication.People" />
<import type="android.text.TextUtils" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{peopleInfo.name}"
android:textColor="@android:color/holo_red_light"
android:textSize="25sp" />
<ViewStub
android:id="@+id/viewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/stubs_layout"
bind:peopleInfo="@{peopleInfo}" />
<Button
android:id="@+id/inflateBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
MainActivity
final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
People people = new People("张三", "22");
activityMainBinding.setPeopleInfo(people);
activityMainBinding.inflateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activityMainBinding.viewStub.getViewStub().inflate();
}
});
如果不想再 xml 中使用 bind:peopleInfo="@{peopleInfo}",也可以在代码里设置,下面请看:
activityMainBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
StubsLayoutBinding stubsLayoutBinding = DataBindingUtil.bind(inflated);
stubsLayoutBinding.setPeopleInfo(people);
}
});
好了,DataBinding 的基本使用就讲到这里,下一篇将会给大家讲解 事件处理 和 注解的高级绑定。