DataBinding框架

本文详细介绍Android DataBinding框架,包括其构建环境配置、基本用法、数据绑定方式、事件处理及ViewStubs使用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

####解决什么问题
减少编写大量的毫无营养的代码, 如 findViewById()、setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等。 通过 Data Binding , 我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的毫无营养的代码了。


####构建环境
在模块build.gradle中加上如下代码
``` html
dataBinding {
    enabled = true
}
``` 



####用法

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


``` html
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
        <!-- 变量名,变量类型 描述了一个布局中会用到的属性 -->
       <variable name="user" type="com.connorlin.databinding.model.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>

``` 
每个 variable 标签描述了会在 binding 表达式中使用的属性。生成的Binding类会有标签的getter/setter方法。
``` java
<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.connorlin.databinding.model.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>
```
在 Binding 类中生成对应的 getters 和 setters:

``` java
public com.connorlin.databinding.model.User getUser();
public void setUser(com.connorlin.databinding.model.User user);
public Drawable getImage();
public void setImage(Drawable image);
public String getNote();
public void setNote(String note);
```
布局中每一个带有 ID 的 View,都会生成一个 public final 字段。binding 过程会做一个简单的赋值,在 binding 类中保存对应 ID 的 View。这种机制相比调用 findViewById 效率更高。
#####数据对象 - (Model)

和原来Model写法一样,用于 TextView 的 android:text 属性的表达式@{user.firstName},当User没有getter/setter方法时,会读取 User 对象的 firstName 字段,反之读取对象的 getFirstName()方法。



#####绑定数据 - (ViewModel)
在默认情况下,每一个布局文件经过编译后会生成一个继承于 ViewDataBinding 的 Binding 类,将它转换成帕斯卡命名并在名字后面接上Binding。例如,布局文件叫 main_activity.xml,所以会生成一个 MainActivityBinding 类。这个类包含了布局文件中所有的绑定关系,会根据绑定方法setData()给布局文件赋值。在 inflate 的时候创建 binding 的方法如下:

``` java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   
   //  ActivityBaseBinding 类是自动生成的
   ActivityBaseBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_base);
   User user = new User("Connor", "Lin");
   // 所有的 set 方法也是根据布局中 variable 名称生成的
   binding.setUser(user);
}
``` 
获取一个View:
``` java
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
```
在ListView、RecyclerView Adapter中使用:
``` java

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
```
Binding类放在模块包名+databinding目录下,更改data标签的class属性可以更改Binding类命令和位置。

``` java
<data class="CustomBinding">
    ...
</data>
```
以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 “.”:

``` java
<data class=".CustomBinding">
    ...
</data>
```
这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中:
``` java
<data class="com.example.CustomBinding">
    ...
</data>
```
#####事件处理
布局中声明要处理事件的方法,类似于 android:onClick 可以指定 Activity 中的函数。
方法调用
监听绑定
区别:
创建监听的时机不同;
方法引用是必须表达式正确,在编译时才用这个方法创建监听,否则不创建监听;而监听绑定无论如何会先创建监听,运行时候调用lambda 表达式指定的方法。二者主要区别在于前者在data binding时就实现了监听。
监听绑定允许表达式中更灵活;
只需要返回值相同,当表达式不能匹配回调时,Data Binding return default值, null for reference types, 0 for int, false for boolean, etc.。
方法引用
以下是个例子:
``` java
public class EventHandler {
    private Context mContext;
    public EventHandler(Context context) {
        mContext = context;
    }

    public void onClickFriend(View view) {
        Toast.makeText(mContext, "onClickFriend", Toast.LENGTH_LONG).show();
    }
}
```
表达式如下:
``` java
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="handler"
            type="com.connorlin.databinding.handler.EventHandler"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handler::onClickFriend}"/>
        <!-- 注意:表达式里的方法签名必须和监听器里的方法签名吻合 -->
    </LinearLayout>
</layout>
```
监听绑定
以下是个例子:

``` java
public void onTaskClick(Task task) {
    task.run();
}
```
表达式如下:
``` java
<?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable
            name="handler" type="com.connorlin.databinding.handler.EventHandler"/>
        <variable
            name="task" type="com.connorlin.databinding.task.Task"/>
      </data>

      <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="match_parent">
          <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> handler.onTaskClick(task)}"/>
      </LinearLayout>
  </layout>
```
关于参数

Listener方法的参数名有两种选择:要么不写,要么就要写全。
``` java
public void onTaskClick(Task task) {
    task.run();
}
```
表达式如下:
``` java
  android:onClick="@{(theView) -> presenter.onSaveClick(task)}
```
也可以这样
``` java  
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

```

导入(Imports)

data 标签内可以有多个 import 标签,作用类似Java
``` java 
<data>
    <import type="android.view.View"/>
</data>
```
<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当类名发生冲突时,可以使用 alias
``` java 
<import type="android.view.View"/>
<import type="com.connorlin.databinding.ui.View" alias="AliasView"/>
```
导入的类型也可以用于变量的类型引用和表达式中
``` java 
<data>
    <import type="com.connorlin.databinding.model.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
</data>
```
注意:Android Studio 还没有对导入提供自动补全的支持。你的应用还是可以被正常编译,要解决这个问题,你可以在变量定义中使用完整的包名。
java.lang.* 包中的类会被自动导入,可以直接使用,例如, 要定义一个 String 类型的变量
binding 类会生成一个命名为 context 的特殊变量(其实就是 rootView 的 getContext() ) 的返回值), **这个变量可用于表达式中**。 如果有名为 context 的变量存在,那么生成的这个 context 特殊变量将被覆盖。
``` java 
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{handler.loadString(context)}"/>
```
``` java 
public String loadString(Context context) {
    // 使用生成的context变量
    return context.getResources().getString(R.string.string_from_context);
}
```
变量可以传递给include布局,但是需要在各个布局中声明
view根标签不可是merge

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

数学计算 + - / * %
字符串连接 +
逻辑 && ||
二进制 & | ^
一元 + - ! ~
位移 >> >>> <<
比较 == > < >= <=
instanceof
组 ()
字面量 - 字符,字符串,数字, null
类型转换
函数调用
字段存取
数组存取 []
三元运算符 ?:

在xml中转义是不可避免的
附:常用的转义字符

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

不支持的操作符

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

this
super
new
显式泛型调用 <T>
Null合并运算符

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

``` java
android:text="@{user.lastName ?? `Default LastName`}"
``` 
等同于

``` java
android:text="@{user.lastName != null ? user.lastName : `Default LastName`}"
``` 
容器类

通用的容器类:数组,lists,sparse lists,和 maps,可以用 [] 操作符来存取

``` java
<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]}"
``` 
字符串常量

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

``` java
android:text='@{map["firstName"]}'
也可以用双引号将属性包起来。这样的话,字符串常量就可以用 " 或者反引号 ( ` ) 来调用 
``` 
``` java
android:text="@{map[`firstName`}"
android:text="@{map[&quot;firstName&quot;]}"
``` 
资源

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

``` java
android:text="@{@string/fullname(user.fullName)"
``` 
字符串格式化和复数形式可以这样实现:

``` java
android:text="@{@plurals/sample_plurals(num)}"
``` 
当复数形式有多个参数时,应该这样写:

``` java
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)



####ViewStubs

ViewStub 相比普通 View 有一些不同。ViewStub 一开始是不可见的,当它们被设置为可见,或者调用 inflate 方法时,ViewStub 会被替换成另外一个布局。

因为 ViewStub 实际上不存在于 View 结构中,binding 类中的类也得移除掉,以便系统回收。因为 binding 类中的 View 都是 final 的,所以Android 提供了一个叫  **ViewStubProxy **的类来代替 ViewStub 。开发者可以使用它来操作 ViewStub,获取 ViewStub inflate 时得到的视图。

**但 inflate 一个新的布局时,必须为新的布局创建一个 binding。**因此, ViewStubProxy 必须监听 ViewStub 的 ViewStub.OnInflateListener,并及时建立 binding。由于 ViewStub 只能有一个 OnInflateListener,你可以将你自己的 listener 设置在 ViewStubProxy 上,在 binding 建立之后, listener 就会被触发。

``` java
mActivityViewStubBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
mActivityViewStubBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        IncludeBinding viewStubBinding = DataBindingUtil.bind(inflated);
        User user = new User("Connor", "Lin", 28);
        viewStubBinding.setUser(user);
    }
});
```
通过 ViewStubProxy 来 inflate ViewStub :
``` java
public void inflate(View view) {
    if (!mActivityViewStubBinding.viewStub.isInflated()) {
        mActivityViewStubBinding.viewStub.getViewStub().inflate();
    }
}
```
此处 isInflated() 和 getViewStub() 会标红,请不要担心,这并不是错误,是 ViewStubProxy 中的方法。
####优缺点
#####缺点
表达式不能自动编译
#####优点






####对比MVP






MVP:
Presenter-->View loadData-->callback-->View.method()
优点
你可以很容易对这种视觉行为进行单元测试。
缺点
XML不被编译,所以经常会在运行时期发现错误,而不是编译期间。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值