Android Data Binding数据绑定详解(基础篇)

文章导航

Android Data Binding数据绑定详解(基础篇)
Android Data Binding数据绑定详解(进阶篇)

简介

在2015年的谷歌IO大会上,Android UI Toolkit团队发布了DataBinding 框架,将数据绑定引入了Android开发。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById 然后手工设置数据了。官方文档说明

构建环境

使用之前,需要在Android Studio中build.gradle配置开启Data Binding,具体如下:

android {
    ....
    dataBinding {
        enabled = true    
    }    
}

Data Binding Layout文件

1.Data Binding表达式

Data Binding layout文件有点不同的是:起始根标签是layout,接下来一个data元素以及一个view的根元素。这个view元素就是你没有使用Data Binding的layout文件的根元素。举例说明如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.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>

在data内type对应的是实体类User,name是实体类的实例。在布局中可以直接通过 @{} 绑定属性值。

2.Data对象

假设你有一个user的plain-old Java Object(POJO):

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

这个类型的对象拥有从不改变的数据。在app中它是常见的,可以读取一次并且之后从不改变。当然也可以使用JavaBeans对象:

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

从Data Binding的角度来看,这两个类是等价的。用于TextView中的android:text属性的表达式 @{user.firstName} 将访问前者POJO对象中的firstName 和后者JavaBeans对象中的getFirstName() 方法。

在Android中接收后台返回实体类,多少难免会有一些空值,而我们在不注意的时候直接使用则会引发空指针这个异常,那么使用Data Binding后,我们则无需关注这块内容。如果使用Data Binding,如果数据出现空时,则默认显示当前类型的默认值。

3.Binding数据

默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”后缀。上述的layout文件是main_activity.xml,因此生成的类名是MainActivityBinding。此类包含从layout属性到layout的Views中所有的bindings(例如user变量),并且它还知道如何给Binding表达式分配数值。

创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

或者你可以通过如下获取View:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

ListView 或者RecyclerView adapter 使用Data Binding时,

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

4.事件处理

4.1 在Activity中定义事件类以及进行初始化:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityBindListenerBinding mBindListenerBinding = DataBindingUtil.setContentView(this, R.layout.activity_bind_listener);
        mBindListenerBinding.setPersenter(new Presenter());
}

初始化这个事件内部类,方式同样有俩种,如下:

  • mBindListenerBinding.setPersenter(new Presenter());
  • mBindListenerBinding.setVariable(BR.persenter,new Persenter());

4.2 布局文件引入命名空间,进行相关调用:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.hlq.databindingdemo.bean.UserBean" />
        <variable
            name="persenter"
            type="com.hlq.databindingdemo.activity.Presenter" />
    </data>
    <android.support.v7.widget.LinearLayoutCompat xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="15dp"
        tools:context="com.hlq.databindingdemo.activity.BindListenerActivity">
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{persenter.onClick}"
            android:text="测试监听器绑定." />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{persenter::onClick}"
            android:text="测试监听器绑定:" />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> persenter.getUserClick()}"
            android:text="测试方法绑定" />
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> persenter.showUserName(user)}"
            android:text="测试方法绑定传值" />
    </android.support.v7.widget.LinearLayoutCompat>
</layout>

其中,调用事件的方式有俩种,分别如下:

  • android:onClick="@{persenter.onClick}"
  • android:onClick="@{persenter::onClick}" 官方更为推崇使用这种。

而Lambda 表达式使用方式如下:

  • android:onClick="@{() -> persenter.getUserClick()}"
  • android:onClick="@{() -> persenter.showUserName(user)}"

4.3 Activity中的内部类Presenter(也可以抽取出去)

 public class Presenter {
        public void onClick(View view) {

        }
        public void getUserClick() {
  
        }
        public void showUserName(UserBean userBean) {

        }
    }

5.深入Layout文件

5.1 Import

import 元素允许你的布局文件引用类,就像在java文件中引用一样:

<data>
    <import type="android.view.View"/>
</data>

现在,View 就可以在表达式中使用了(例如, View.VISIBLE):

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

当类名有冲突时,其中一个类名可以重命名为alias,在该layout文件中Vista 对应com.example.real.estate.View,而View对应android.view.View

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

导入的类型可以在Variable和表达式中使用作为引用来使用:
注意:Android Studio还没有处理imports,所以自动导入Variable在你的IDE不能使用。您的app仍会正常编译,你可以在您的Variable定义中使用完全符合规定的名称来解决该IDE问题。

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
 </data>
 
...

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

导入的类型还可以在表达式中使用static属性和方法:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data><TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

5.2 自定义Binding类名称

默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding封装包下。例如,所述layout文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放置在com.example.my.app.databinding

Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:

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

在模块封装包的databinding包中会生成名为ContactItem的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.,如下:

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

在这个情况下,ContactItem类直接在模块包种生成。或者你可以提供整个包名:

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

5.3 Includes

首先,编写一个layout,由于我们需要将数据源也传递过去,所以这里需要提前声明下实体。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.hlq.databindingdemo.bean.UserBean" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            style="@style/titleStyle"
            android:text='@{"姓名:"+user.userName}' />
        <TextView
            style="@style/titleStyle"
            android:text='@{"年龄:"+user.userAge}' />
        <TextView
            style="@style/titleStyle"
            android:text='@{"地址:"+user.userAddress}' />
    </LinearLayout>
</layout>

在所对应的Activity layout中通过bind绑定数据源即可:

<?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"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="user"
            type="com.hlq.databindingdemo.bean.UserBean" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.hlq.databindingdemo.activity.IncludeActivity">
        <TextView
            style="@style/contentStyle"
            android:text="include引用布局示例" />
        <include
            layout="@layout/include_item_layout"
            bind:user="@{user}" />
    </LinearLayout>
</layout>

Activity中初始化数据源:

    ActivityIncludeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_include);
    binding.setUser(new UserBean("静心Study", "22", "帝都"));

注意:绑定数据不支持 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="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

5.4 ViewStub

首先编写一个viewStub的layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        style="@style/contentStyle"
        android:text="今天上班了" />
    <TextView
        style="@style/titleStyle"
        android:text="难受想哭,心里默默MMP" />
</LinearLayout>

编写布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.hlq.databindingdemo.activity.ViewStubActivity">
        <TextView
            style="@style/contentStyle"
            android:text="ViewStub示例" />
        <ViewStub
            android:id="@+id/viewStub"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout="@layout/layout_view_stub" />
    </LinearLayout>
</layout>

Activity进行初始化:

 ActivityViewStubBinding viewStubBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
    viewStubBinding.viewStub.getViewStub().inflate();

5.5 表达式

(a)常用表达式跟Java表达式很像,以下这些是一样的:

  • 数学 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元运算 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 分组 ()
  • null
  • Cast
  • 方法调用
  • 数据访问 []
  • 三元运算 ?:

示例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

(b)缺少的操作:

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

(c)Null合并操作

?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象: android:text="@{user.displayName ?? user.lastName}"

(d)集合

常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]来访问。

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

(e)字符串

当使用单引号包含属性值时,在表达式中使用双引号很容易:

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

使用双引号来包含属性值也是可以的。字符串前后需要使用“`”:

android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"

(f)Resources

使用正常的表达式来访问resources也是可行的:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式化字符串和复数可以通过提供参数来判断

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

当复数需要多个参数时,所有的参数都会通过:

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

下面一些资源的显示调用跟普通的调用略有不同:

类型 正常引用 表达式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

5.6 Data 对象

任何Plain old Java object(PO​​JO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable collections

当这些可观察Data对象​​绑定到UI,Data对象属性的更改后,UI也将自动更新。

(a)Observable 对象

实现android.databinding.Observable接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。
Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable的基类为实现监听器注册机制而创建。

通过指定一个Bindable注解给getter;
在setter内通过notifyPropertyChanged(BR.classNo)设置更新。

public class ClassBean extends BaseObservable {
    private String classNo;
    private String classNum;
    private String className;
    public ClassBean(String className) {
        this.className = className;
    }
    public ClassBean(String className, String classNum) {
        this.className = className;
        this.classNum = classNum;
    }
    public ClassBean(String classNo, String classNum, String className) {
        this.classNo = classNo;
        this.classNum = classNum;
        this.className = className;
    }
    @Bindable
    public String getClassNo() {
        return classNo;
    }
    @Bindable
    public String getClassName() {
        return className;
    }
    @Bindable
    public String getClassNum() {
        return classNum;
    }
    public void setClassNo(String classNo) {
        this.classNo = classNo;
        notifyPropertyChanged(BR.classNo);
    }
    public void setClassName(String className) {
        this.className = className;
        notifyPropertyChanged(BR.classNo);
    }
    public void setClassNum(String classNum) {
        this.classNum = classNum;
        notifyPropertyChanged(BR.classNo);
    }
}

动态修改实体,从而进行UI即时刷新。

    private ActivityObservableBinding mBinding;
    private ClassBean mClassBean = new ClassBean("001", "100", "A1T105");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_observable);
        mBinding.setClassX(mClassBean);
        mBinding.setPresenter(new Presenter());
    }
    //修改实体,UI界面即使刷新
    public class Presenter {
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            mClassBean.setClassNo("No:" + s);
            mClassBean.setClassNum("Num:" + s);
            mClassBean.setClassName("Name:" + s);
        }
    }

  • notifyPropertyChanged(BR.classNo);
    用于设置单个更新字段,希望哪个字段更新就给哪个字段设置

  • notifyChange();
    用于更新所有字段,只需要给一个字段设置,所有的字段都会更新

(b)Observable 字段

ObservableFields是自包含具有单个字段的observable对象。

Step 1: 定义实体类

public class MissBean {
    public final ObservableField<String> missWho = new ObservableField<>();
    public final ObservableField<String> missYou = new ObservableField<>();
}

Step 2: 布局引用

引入命名空间。

<data>
    <variable
        name="missBean"
        type="com.hlq.databindingdemo.bean.MissBean" />
</data>

设置对应值。

<TextView
    style="@style/titleStyle"
    android:text="@{missBean.missWho}" />
<TextView
    style="@style/titleStyle"
    android:text="@{missBean.missYou}" />

Step 3: Activity中初始化 - 赋值

mMissBean = new MissBean();
mMissBean.missWho.set("在思念谁?");
mMissBean.missYou.set("在思念你~");
//取值
//mMissBean.missYou.get();
mBinding.setMissBean(mMissBean);

(c)Observable 集合

ObservableArrayList 用于键是整数

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在layout文件中,通过索引可以访问list:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object>"/>
</data><TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

这里需要注意,指定type时,ObservableArrayList类型里面不能包含<,而是通过 < ; 代替。

ObservableArrayMap用于键是引用类型,如String。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在layout文件中,通过String键可以访问map:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object>"/>
</data><TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

下一篇:Android Data Binding数据绑定详解(进阶篇)

发布了63 篇原创文章 · 获赞 1 · 访问量 2101

猜你喜欢

转载自blog.csdn.net/weixin_42046829/article/details/104959730