Android之Databinding学习笔记

时间:2021-09-30 18:36:31

Android之DataBinding学习笔记

简介

Data binding 在2015年7月发布的Android Studio v1.3.0 版本上引入,在2016年4月Android Studio v2.0.0 上正式支持。目前为止,Data Binding 已经支持双向绑定了。

Databinding 是一个实现数据和UI绑定的框架,是一个实现 MVVM 模式的工具,有了 Data Binding,在Android中也可以很方便的实现MVVM开发模式。

Data Binding 是一个support库,最低支持到Android 2.1(API Level 7+)。

Data Binding 之前,我们不可避免地要编写大量的重复的代码,如 findViewById()、setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的重复的代码了。

studio环境构建

1.在模块的build.gradle文件中添加dataBinding配置

android {  
......

dataBinding{
enabled = true
}

......
}

注意:如果app依赖了一个使用 Data Binding 的库,那么app module 的 build.gradle 也必须配置 Data Binding

Data Binding 布局文件 - (View)

Data binding 的布局文件与传统布局文件有一点不同。它以一个 layout 标签作为根节点,里面是 data 标签与 view 标签。view 标签的内容就是不使用 Data Binding 时的普通布局文件内容。以下是一个例子:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>

</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.itrealman.databindingdemo.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="@{entry.text}"
android:textColor="@{entry.color}"/>

</RelativeLayout>
</layout>

在上面的布局中,对于data里面的variable nama=”entry”表示的是一个变量名entry,type表示你所对应实体的包名路径,在TextView中,我们采用@{}的语法来引用实体类Entry中的属性。

数据对象 - (Model)

Entry.java

public class Entry {
private String text;
private int color;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public int getColor() {
return color;
}

public void setColor(int color) {
this.color = color;
}

}

上面就是我们所说的实体类Entry,用于TextView的android:text属性的表达式@{entry.text}android:textColor属性表达式@{entry.color}

绑定数据 - (ViewModel)

在默认情况下,会基于布局文件生成一个继承于 ViewDataBinding 的 Binding 类,将它转换成和你的布局命名并在名字后面接上Binding。例如,布局文件叫 activity_main.xml,所以会生成一个 ActivityMainBinding类。这个类包含了布局文件中所有的绑定关系,会根据绑定表达式给布局文件赋值。在 inflate 的时候创建 binding 的方法如下:

MainActivity.java

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
Entry entry = new Entry();
entry.setText("文本数据1");
entry.setColor(0xff0000ff);
binding.setEntry(entry);
}
}

事件处理

类似于 android:onClick 可以指定 Activity 中的函数,Data Binding 也允许处理从视图中发送的事件。

有两种实现方式:

  • 方法调用

  • 监听绑定

二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。

方法调用

相较于 android:onClick ,它的优势在于表达式会在编译时处理,如果函数不存在或者函数签名不对,编译将会报错。

以下是个例子:

Entry.java

public class Entry {
private String text;
private int color;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public int getColor() {
return color;
}

public void setColor(int color) {
this.color = color;
}
//在这个实体中添加一个点击时间的方法
public void onClick(View view){
Toast.makeText(view.getContext(),"已点击",Toast.LENGTH_SHORT).show();
}

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>

</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.itrealman.databindingdemo.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="@{entry.text}"
android:textColor="@{entry.color}"
android:onClick="@{entry::onClick}"/>

</RelativeLayout>
</layout>

通过在上面的TextView中添加一个android:onClick属性表达式为@{entry.onClick}这样就能调用点击事件 了,(注意:对应的方法名和监听器对象必须对应) 如果该方法不存在,则在编译的时候就不会通过了。除了上面的表达式可以表示之外,我们还可以使用以下的表达式方式进行设置:

android:onClick="@{entry.onClick}"(不过这种方式已经过时了,因为这样的表示会和entry.text这样的属性引用难以区分)

监听绑定

监听绑定在事件发生时调用,可以使用任意表达式

此功能在 Android Gradle Plugin version 2.0 或更新版本上可用.

在方法引用中,方法的参数必须与监听器对象的参数相匹配。在监听绑定中,只要返回值与监听器对象的预期返回值相匹配即可。

Entry.java

public class Entry {
private String text;
private int color;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public int getColor() {
return color;
}

public void setColor(int color) {
this.color = color;
}

//将点击监听事件中添加了一个参数
public void onClick(View view,String str){
Toast.makeText(view.getContext(),"已点击,产生了" + str,Toast.LENGTH_SHORT).show();
}

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>

<variable
name="str"
type="String"/>

</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.itrealman.databindingdemo.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="@{entry.text}"
android:textColor="@{entry.color}"
android:onClick="@{(v) -> entry.onClick(v,str)}"/>

</RelativeLayout>
</layout>

MainAcitivty.java

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main,new MyComponent(this));
Entry entry = new Entry();
entry.setText("文本数据1");
entry.setColor(0xff0000ff);
//设置测试字符串
binding.setStr("我是监听绑定的数据测试");
binding.setEntry(entry);
}
}

当一个回调函数在表达式中使用时,数据绑定会自动为事件创建必要的监听器并注册监听。关于android:onClick="@{(v) -> entry.onClick(v,str)}"这里采用的是java1.8中的lambda表达式的形式,因为这样我们可以传递相应的参数进去。

导入(Imports)

1.data 标签内可以有多个 import 标签。你可以在布局文件中像使用 Java 一样导入引用

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<import type="android.view.View"/>
<variable
name="show"
type="boolean"/>

</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.itrealman.databindingdemo.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="@{show ? View.VISIBLE : View.GONE}"
android:textSize="20sp"/>

</RelativeLayout>
</layout>

2.当类名发生冲突时,可以使用 alias

<import type="android.view.View"/>
<import type="cn.itrealman.databindingdemo.utils.View" alias="UtilsView"/>

3.导入的类型也可以用于变量的类型引用和表达式中

<data>
<import type="cn.itrealman.databindingdemo.Entry"/>
<import type="java.util.List"/>
<variable name="entry" type="Entry"/>
<variable name="entryList" type="List<Entry>"/>
</data>

注意:Android Studio 还没有对导入提供自动补全的支持。你的应用还是可以被正常编译,要解决这个问题,你可以在变量定义中使用完整的包名。

4.导入也可以用于在表达式中使用静态方法

StringUtils.java

public class StringUtils {
public static String show(String string){
string = string.toUpperCase();
return string;
}
}

activity_main.xml

    <data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>
<import type="cn.itrealman.databindingdemo.utils.StringUtils"/>
</data>
......
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="@{StringUtils.show(entry.text)}"
android:textColor="@{entry.color}"/>
</layout>

5.java.lang.* 包中的类会被自动导入,可以直接使用,例如, 要定义一个 String 类型的变量

<variable name="str" type="String" />

变量 Variables

1.data 标签中可以有任意数量的 variable 标签。每个 variable 标签描述了会在 binding 表达式中使用的属性。

<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="entry" type="cn.itrealman.databindingdemo.Entry"/>
<variable name="image" type="Drawable"/>
<variable name="str" type="String"/>
</data>

2.可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法。

<TextView
android:id="@+id/m_entry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={entry.text}" />


<TextView
android:text="@{user.entry}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{mEntry.getVisibility() == View.GONE ? View.GONE : View.VISIBLE}" />

<!-- 这里TextView直接引用第一个TextView,mEntry为m_entry id的驼峰命名,默认会去掉下划线 -->

3.binding 类会生成一个命名为 context 的特殊变量(其实就是 rootView 的 getContext() ) 的返回值),这个变量可用于表达式中。 如果有名为 context 的变量存在,那么生成的这个 context 特殊变量将被覆盖。

StringUtils.java

public class StringUtils {
public static String show(Context context,String string){
Toast.makeText(context, string,Toast.LENGTH_SHORT).show();
string = string.toUpperCase();
return string;
}
}
<data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>
<import type="cn.itrealman.databindingdemo.utils.StringUtils"/>
</data>
......
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="@{StringUtils.show(context,entry.text)}"
android:textColor="@{entry.color}"/>

自定义绑定类名

默认情况下,binding 类的名称取决于布局文件的命名,以大写字母开头,移除下划线,后续字母大写并追加 “Binding” 结尾。这个类会被放置在 databinding 包中。举个例子,布局文件 activity_main.xml 会生成 ActivityMainBinding 类。如果 module 包名为 com.example.my.app ,binding 类会被放在 com.example.my.app.databinding 中。

通过修改 data 标签中的 class 属性,可以修改 Binding 类的命名与位置。举个例子:

<data class="CustomBinding">
...
</data>

以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 “.”:

<data class=".CustomBinding">
...
</data>

这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中:

<data class="com.example.CustomBinding">
...
</data>

Includes

<?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="entry"
type="cn.itrealman.databindingdemo.Entry"/>

</data>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<include layout="@layout/include"
app:entry="@{entry}"/>

</RelativeLayout>
</layout>

include.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="entry"
type="cn.itrealman.databindingdemo.Entry"/>

</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{entry.text}"
android:textColor="@{entry.color}">


</TextView>
</layout>

需要注意, activity_main.xml 与 include.xml 中都需要声明 user 变量。

Data binding 不支持直接包含 merge 节点。举个例子, 以下的代码不能正常运行 :

<?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="entry"
type="cn.itrealman.databindingdemo.Entry"/>

</data>
<merge>
<include layout="@layout/include"
app:entry="@{entry}"/>

</merge>
</layout>

表达式语言

表达式语言与 Java 表达式有很多相似之处。下面是相同之处:

  • 数学计算 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元 + - ! ~
  • 位移 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 组 ()
  • 字面量 - 字符,字符串,数字, null
  • 类型转换
  • 函数调用
  • 字段存取
  • 数组存取 []
  • 三元运算符 ?:
<!-- 内部使用字符串 & 字符拼接-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`num:` + String.valueOf(entry.num)}"/>


<!-- 三目运算-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{show ? View.VISIBLE : View.GONE}"/>

在xml中转义是不可避免的,如 : 使用“&&”是编译不通过的,需要使用转义字符 “&&”

附:常用的转义字符

描述 转义字符 十进制 显示结果
空格 &nbsp; &#160;  
小于号 &lt; &#60; <
大于号 &gt; &#62; >
与号 &amp; &#38; &
引号 &quot; &#34; "
撇号 &apos; &#39; '
乘号 &times; &#215; ×
除号 &divide; &#247; ÷

不支持的操作符

一些 Java 中的操作符在表达式语法中不能使用。

  • this
  • super
  • new
  • 显示泛型调用<T>

Null合并运算符

Null合并运算符 ?? 会在非 null 的时候选择左边的操作,反之选择右边。

android:text="@{entry.text ?? `Default Text`}"

等同于

android:text="@{entry.text != null ? entry.text : `Default Text`}"

容器类

通用的容器类:数组,List ,SparseArray ,和 Map,可以用 [] 操作符来存取

<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]}"

字符串常量

使用单引号把属性包起来,就可以很简单地在表达式中使用双引号:

android:text='@{map["text"]}'

也可以用双引号将属性包起来。这样的话,字符串常量就可以用 ” 或者反引号 ( ` ) 来调用

android:text="@{map[`text`}"
android:text="@{map[&quot;text&quot;]}"

资源

也可以在表达式中使用普通的语法来引用资源:

android:text="@{@string/text(entry.text)"

字符串格式化和复数形式可以这样实现:

android:text="@{@plurals/sample_plurals(num)}"

当复数形式有多个参数时,应该这样写:

android:text="@{@plurals/numbers(num, num)}"
Type Normal Reference Expression Reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

数据对象 (Data Objects)

任何 POJO 对象都能用在 Data Binding 中,但是更改 POJO 并不会同步更新 UI。Data Binding 的强大之处就在于它可以让你的数据拥有更新通知的能力。

  • Observable 对象

  • Observable 字段

  • Observable 容器类

当以上的 observable 对象绑定在 UI 上,数据发生变化时,UI 就会同步更新。

Observable 对象

Observable 接口有一个添加/移除 listener 的机制,但通知取决于开发者。为了简化开发,Android 原生提供了一个基类 BaseObservable 来实现 listener 注册机制。这个类也实现了字段变动的通知,只需要在 getter 上使用 Bindable 注解,并在 setter 中通知更新即可。

public class Entry extends BaseObservable{
private String text;
private int color;

@Bindable
public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
notifyPropertyChanged(BR.text);

}
@Bindable
public int getColor() {
return color;
}

public void setColor(int color) {
this.color = color;
notifyPropertyChanged(BR.color);
}

public void onClick(View view,String str){
Toast.makeText(view.getContext(),"已点击,产生了" + str,Toast.LENGTH_SHORT).show();
setText(str);
setColor(0xffff0000);
}

}

BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry。
当点击 事件使数据发生变化时需要调用 notifyPropertyChanged(BR.text) 通知系统 BR.text 这个 entry 的数据已经发生变化以更新UI。

ObservableFields

创建 Observable 类还是需要花费一点时间的,如果想要省时,或者数据类的字段很少的话,可以使用 ObservableField 以及它的派生 ObservableBoolean、ObservableByte 、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、
ObservableParcelable 。

ObservableFields 是包含 observable 对象的单一字段。原始版本避免了在存取过程中做打包/解包操作。要使用它,在数据类中创建一个 public final 字段:

public class EntryField{
public ObservableField<String> mText = new ObservableField<>();
public ObservableField<Integer> mColor = new ObservableField<>();

public EntryField(String text, int color) {
mText.set(text);
mColor.set(color);
}
}

要存取数据,只需要使用 get() / set() 方法:

mEntryField.mText.set("text");
mEntryField.mColor.set(0xffff0000);

String text = mEntryField.mText.get();
String color = mEntryField.mColor.get();

Observable Collections 容器类

一些应用会使用更加灵活的结构来保持数据。Observable 容器类允许使用 key 来获取这类数据。当 key 是类似 String 的一类引用类型时,使用 ObservableArrayMap 会非常方便。

ObservableArrayMap<String, Object> mEntry = new ObservableArrayMap<>();
mEntry .put("text", "it_real_man");
mEntry .put("color", 0xffff0000);
binding.setEntry(mEntry);

在布局中,可以用 String key 来获取 map 中的数据:

<data>
<import type="android.databinding.ObservableMap"/>
<variable name="entry" type="ObservableMap&lt;String, String&gt;"/>
</data>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{entry["text"]}'/>


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor='@{entry["color"]}'/>

当 key 是整数类型时,可以使用 ObservableArrayList :

ObservableArrayList<String> sEntry= new ObservableArrayList<>();
sEntry.add("entryData");
sEntry.add("text");
sEntry.add("color");
binding.setSEntry(sEntry);

在布局文件中,使用下标获取列表数据:

<data>
<import type = "android.databinding.ObservableList"/>
<variable
name="sEntry"
type="ObservableList&lt;String&gt;"/>

</data>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{sEntry[0]}'/>


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{sEntry[1]}'/>


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{sEntry[2]}'/>