Android DataBinding 使用教程(一)

用了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 和 agegetter 和 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.namepeople.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> 里引入的类设置实例。这里有两种方法:

  1. activityMainBinding.setPeople(people);
  2. 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&lt;String&gt;" />
    </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 访问 []
  • 三元 ?:

不支持的运算符:

  • this
  • super
  • new
  • 显示泛型调用

空合并运算符(??

传统写法: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}" />

可以为格式化stringplurals提供参数

这一块很多 博客都没有讲明白,都是照搬谷歌官网的例子,导致很多读者看到这个也有点懵,作为新时代的青年博主,有责任为大家解惑

<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 的基本使用就讲到这里,下一篇将会给大家讲解 事件处理 和 注解的高级绑定。

Android DataBinding 使用教程(二)

 
发布了6 篇原创文章 · 获赞 2 · 访问量 411

猜你喜欢

转载自blog.csdn.net/tocong2015/article/details/103403888