DataBinding
是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM
模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter
层替换成了 ViewModel
层。DataBinding
能够省去我们一直以来的 findViewById()
步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
findViewById的原理:Android中的View结构是一个树形结构,findViewById
就是自树的根节点,依次遍历其子节点,知道找到目标的id。
Fragment
中使用容易犯错。从原理图可以看到,在Activity中调用findViewById,实际上是调用Window中的findViewById,但是Fragment中并没有单独的Window,Fragment中调用findViewById的效果和Activity中调用的效果一模一样。所以如果一个Activity中有多个Fragment,Fragment中的控件名称又有重复的,那直接findViewById会出错的;onCreate
方法中被引用,我们不能方便的将xml中的控件与代码中的控件结合起来,特别是如果xml的命名与代码中的命名又不规范,代码阅读起来简直就是噩梦。databinding
会根据动态生成一个ActivityMainBinding
文件,在执行ActivityMainBinding.inflate
的时候,会自动生成控件的引用(mapBindings
方法),这里对布局tree执行一趟遍历查找就可以生成所有的引用
DataBinding的优点
DataBinding的缺点
原文链接
参考博客
原文
Data Binding 的结构[Top]
作为在 Android 开发中体现 MVVM 架构思想的 Data Binding,其核心是 观察者模式 的特定实现。首先,它有三个主要的实体:
与 View 相关的数据,它可以是 View 的可观察者对象;
展示给用户的视图,如果有交互功能且能更新数据,它可以是 Data 的可观察者对象;
连接 Data 和 View 的中介,当 Data 或 View 作为可观察者对象时,它充当可观察者对象的代理。假如当我们写了一个名为 demo.xml 的 Data Binding 的 layout 文件后,编译工具会生成一个相应的类——DemoBinding,它的原型就是 ViewDataBinding。我们通常通过 DataBindingUtil.inflate(inflater, R.layout.demo, container, false) 来实例化的 DemoBinding 对象,即 ViewDataBinding。
其次,它主要提供了以下三个方面的功能:
将特定的 View 与特定的 Data 进行绑定,便于模块化;
View 自动感知和响应 Data 的变化,使得处理数据的业务层不必关心 View 的状态,便于解耦;
Data 也可以自动同步带有交互功能的 View 对数据的修改,使得 UI 层的交互不必担心数据是否能同步 View 状态的问题,仍然便于解耦。
基于这三个功能,Data Binding 的结构也可以划分为三个行为模式,以下一一介绍:
首先,Data 往往是一个数据的集合,数据绑定的第一步就是要将整个 Data 集合绑定到 View,比如初始化和数据的整体更新,如下图所示:
可以观察到,rebind 的过程就是一个简单的赋值操作,将 View 的值设置为 Data,只不过由 ViewDataBinding
这个代理来完成这个工作。图中的 _all 参数表示将 View 的所有需要更新的节点都设置为 Data 的所有对应的成员值。当我们在 layout 文件中进行如下设置时,ViewDataBinding
将代理完成 View 中所有数据绑定节点的 data rebind
操作:
有时候,我们并不需要每次更新整个 Data 集合,而只需要更新集合中的某一个成员。我们希望看到的结果是,当 Data.element_i 发生变化
的时候,View.child_i 更新就可以了,而不需要将 View 的所有视图节点都重新渲染一遍。要做到这一点,我们必须要让 View 可以观察 Data 的行为。换句话说,Data 是一个可观察者对象——这是 Data Binding 中另一个魅力所在,其行为模式如下:
我们可以将任何数据作为一个 Observable,然后将 ViewDataBinding 作为 View 的代理观察者,订阅 Data 的成员变化,一旦 Data 成员变化,便通知所有观察者对象——即 ViewDataBinding,然后 ViewDataBinding 再将 View 的相应节点的值设置为 Data 相应成员的新值——即图中的 _member 参数。
这里有三个关键部分:
BaseObservable
:可观察者基类(实际的祖先基类是一个 Observable 接口),实现改接口后,ViewDataBinding 就会在每次 rebind 的时候去订阅 Data 的变化;
@Bindable 标注
:声明该成员是可被观察的,以及在 layout 中可以以 http://Data.xxx(标注的方法名如果为 getXXX)的方式进行访问;
notifyPropertyChanged 方法
:BaseObservable 用于通知具体成员发送变化的方法,只要该方法被调用,ViewDataBinding 就会检索出是哪一个 element 的变化,并只对 View 相应的节点进行更新。
在开发中,根据业务需求,我们一般能遇到两种类型的 View:
一种是只用于展示的 View,它只展示 UI 状态,而不反馈状态,我们称之为 单工View;
另一种除了展示以外,还会反馈状态给监听者,我们称之为 双工View。
在 Android 的 UI事件流中,因为所有的 View 都是可以反馈状态的,所以准确来说,所有的 View 其实都是双工的。我们在这里区分单工和双工是针对业务需求的,比如:我们很多的视图只需要它们展示就可以了,不需要监听它们的状态变化,那么我们将其归为单工View。
费尽心思进行这样的划分,是因为,单工View 只需要有 observe data 行为就可以了;而双工View 往往就需要 observe view 行为。具体来说,在反馈状态时需要更新 Data 的双工 View,我们需要进行 observe view 行为。
因为双工View 会更新 Data,所以为了保证数据的一致性,Data 需要观察双工View 的状态变化。要做到这一点,这样的双工View 必须是一个可观察者对象。得益于 UI事件流的实现,双工View天然是可观察的(只要能反馈状态,就意味着能被观察)。在自定义的双工View中,可以间接引用 ViewDataBinding,这样 ViewDataBinding 就可以代理 Data 订阅 View 的状态变化:
启用 DataBinding 的方法是在对应 Model :app
的 build.gradle
文件里加入以下代码,同步后就能引入对 DataBinding
的支持
android {
dataBinding {
enabled = true
}
}
1、在布局文件中绑定指定的变量打开布局文件,选中布局文件的第一行,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则
和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Person)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Person 之间的通道
Person类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在 data 标签里声明要使用到的变量名、类的全路径(如果 Person类型要多处用到,也可以直接将之 import 进来,就不用每次都指明整个包名路径)
<data>
<variable
name="per1"
type="com.example.databindingpro.Person" />
<variable
name="work"
type="String" />
</data>
如果存在 import 的类名相同的情况,可以使用 alias 指定别名
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<import
alias="TempUser"
type="com.leavesc.databinding_demo.model2.User" />
<variable
name="userInfo"
type="User" />
<variable
name="tempUserInfo"
type="TempUser" />
</data>
这里声明了一个 Person
类型的变量per1
,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置per1
的变量值同时使 TextView
显示相应的文本 完整的布局代码如下所示
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="per1"
type="com.example.databindingpro.Person" />
<variable
name="work"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<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="@{per1.name}"
android:textSize="30dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(per1.age)}"
android:textSize="30dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{work}"
android:textSize="30dp"/>
</LinearLayout>
</LinearLayout>
</layout>
通过 @{per1.name
}使 TextView 引用到相关的变量,DataBinding
会将之映射到相应的 getter
方法 之后可以在 Activity 中通过 DataBindingUtil
设置布局文件,省略原先 Activity 的 setContentView()
方法,并为变量per1
赋值
import androidx.databinding.DataBindingUtil;
import android.app.Activity;
import android.os.Bundle;
import com.example.databindingpro.databinding.ActivityMainBinding;
public class MainActivity extends Activity {
private ActivityMainBinding mActivityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
mActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
com.example.databindingpro.Person person = new com.example.databindingpro.Person("张三",23);
mActivityMainBinding.setPer1(person);//给布局文件name="per1"传入数据,类型为Person对象
mActivityMainBinding.setWork("Teacher");//给布局文件name="work"传入数据,类型为String字符串
}
}