Databinding 基础入门

参考:
官方文档
Data Binding Guide(中文版 - Data Binding(数据绑定)用户指南)
基于中文版的另一版教程

整体概述:

1.环境
2.简单使用
    -- 1).布局文件定义
    -- 2).Data数据
    -- 3).Binding数据
3.深入Layout
    -- 1)导入import
    -- 2) Variables详解
    -- 3)自定义Binding类名
    -- 4)includes使用
    -- 5)常用表达式
4.Data对象
    -- 1)Observable对象
    -- 1)Observable字段
    -- 1)Observable集合
5.Binding生成
    -- 1) 带 ID 的 View
    -- 2)ViewStubs 
    -- 3) Dynamic Variables(RecycleView使用)
6 属性Setters
7.转换器 (Converters) 
8.绑定事件处理方法
9.BindingAdapter的使用场景
10.双向绑定

1.环境

Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本。

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

2.简单使用

1).布局文件定义
<?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>

user变量属性 type为引用的Data对象或者其他对象

<data>
       <variable name="user" type="com.example.User"/>
</data>

属性引用

@{user.firstName} 
@{user.lastName}
2)Data数据
Pojo对象
public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

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;
   }
}

注意:

Pojo对象特点:一旦拥有数据,不会改变
JavaBeans对象:可以自定义数据输出,在DataBinding中,实际获取的值是get属性方法,如果不使用自定义get方法,默认Pojo和JavaBeans对象是一样的。(不建议同时使用,否则会出现数据不一致的情况)

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);

//1.直接使用绑定并setContentView
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   如想获取View对象,可使用
   MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());


//2.ListView或者RecyclerView adapter使用Data Binding时
   ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
   如想获取View对象,可使用
   ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);



   User user = new User("Test", "User");
   binding.setUser(user);
}

建议分开绑定,当使用不同的机制载入layout时,使用下面方式:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

3.深入Layout

1)导入import

注意:java.lang.* 包中的类会被自动导入,可直接使用

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

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

当类名有冲突时,可以让其中一个加一个别名来使用:

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

使用方法建议:

1.当只是使用类Static属性和方法,不需要额外再设置属性,可直接使用以下方式

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

2.当需要使用类所有属性和方法,并需要设置属性,例如model数据,使用以下方式。

<variable
            name="pageNew"
            type="com.gson.HomePageNew"/>
或者
<import type="com.gson.HomePageNew">
<variable   name="pageNew"
                  type="HomePageNew"/> 
2) Variables详解:

在data中可以使用任意数量的variable元素。每一个variable元素描述了一个用于layout文件中Binding表达式的属性。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

注意:

  • 1.Variables类型在编译时被检查,如果一个变量实现了可观察的或可观察到的集合,就应该在类型中反映粗来,如果这个变量是一个类/接口,但没有实现可观察的接口eg:BaseObservable,那么该变量不会被观察到。
  • 2.当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。
  • 3.每一个Binding类中属性,都会有一个默认的属性eg:null(引用) 0(int)false(boolean) ,直到setter调用。
3)自定义Binding类名

一般直接使用layout对应的名称

<data class="ContactItem">
    ...
</data>
4)includes

通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:

1.上一层Layout
<?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>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

2.下一层layout
<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="android.view.View"></import>
        <variable
            name="user"
            type="com.example.User"></variable>
    </data>
    。。。。
</layout>

注意:在name.xml以及contact.xml两个layout文件中必需要有user variable

5)常用表达式

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

eg:

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

this
super
new

显式泛型调用

  • Null合并操作
  android:text="@{user.displayName ?? user.lastName}"
                  ||等价  
  android:text="@{user.displayName != null ? user.displayName : user.lastName}"

  • 属性引用
android:text="@{user.lastName}"
  • 集合使用
<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["firstName"]}'

双引号包含属性(缺点:不能使用 ”app“ + ”name“ 的形式拼接

android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
  • Resources的使用:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
  • 格式化字符串
android:text="@{@string/nameFormat(firstName, lastName)}"
<string name="nameFormat">%s, %s</string>

当复数需要多个参数时

Strings类
 <plurals name="orange">
        <item quantity="one">Have an orange</item>
        <item quantity="other">Have %d oranges</item>
  </plurals>
xml:  
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
  • 一些资源需要明确类型调用。
Type【类型】 Normal Reference【正常引用】 Expression Reference【表达式引用】
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
colorint @color @color
ColorStateList @color @colorStateList

4.Data对象----->ObservableActivity.java

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

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

1) Observable对象:

实现android.databinding.Observable接口的类可以允许附加一个监听器到Bind对象以便监听对象上的所有属性的变化。

Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable注解给getter以及setter内通知来完成的。(注意在get方法上加上@Bindable注释,同时记得给xml中对应的binding赋值,eg: binding.setResult(scanResult);)

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getFirstName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

在编译期间,Bindable注解在BR类文件中生成一个EntryBR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable接口通过方便的PropertyChangeRegistry来实现用于储存和有效地通知监听器。

2) Observable 字段

一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFieldsObservableFields是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

使用方法:

user.firstName.set("Google");
int age = user.age.get();
3)Observable 集合

系统为我们提供了所有的 primitive type 所对应的Observable类,eg:ObservableIntObservableFloatObservableBooleanObservableField 对应着 reference type

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<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"/>

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<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"/>

5.Binding生成

1) 带 ID 的 View

Data Binding 有效降低了代码的冗余性,甚至完全没有必要再去获取一个 View实例,但是情况不是绝对的,万一我们真的就需要了呢?不用担心,只要给View定义一个 ID,Data Binding 就会为我们生成一个对应的 final 变量。

<TextView
    android:id="@+id/firstName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

上面代码中定义了一个 ID 为 firstNameTextView,那么它对应的变量就是

public final TextView firstName;
2)ViewStubs -----> ViewStubActivity.java

ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。

由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。

当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy必需监听ViewStub的OnInflateListener监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy允许开发者在其上设置一个OnInflateListener它会在建立Binding后调用。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        ...>
        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            ... />
    </LinearLayout>
</layout>

//必须在设置监听后去设置binding

binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
   @Override
   public void onInflate(ViewStub stub, View inflated) {
       ViewStubBinding binding = DataBindingUtil.bind(inflated);
       User user = new User("fee", "lang");
       binding.setUser(user);
   }
});
3) Dynamic Variables---->DynamicActivity.class

以 RecyclerView 为例,Adapter 的 DataBinding 需要动态生成,因此我们可以在 onCreateViewHolder 的时候创建这个 DataBinding,然后在 onBindViewHolder 中获取这个 DataBinding。

public static class BindingHolder extends RecyclerView.ViewHolder {
    private ViewDataBinding binding;

    public BindingHolder(View itemView) {
        super(itemView);
    }

    public ViewDataBinding getBinding() {
        return binding;
    }

    public void setBinding(ViewDataBinding binding) {
        this.binding = binding;
    }
}

@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    ViewDataBinding binding = DataBindingUtil.inflate(
            LayoutInflater.from(viewGroup.getContext()),
            R.layout.list_item,
            viewGroup,
            false);
    BindingHolder holder = new BindingHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
}

@Override
public void onBindViewHolder(BindingHolder holder, int position) {
    User user = users.get(position);
    holder.getBinding().setVariable(BR.user, user);
    holder.getBinding().executePendingBindings();
}

注意此处 DataBindingUtil 的用法:

ViewDataBinding binding = DataBindingUtil.inflate(
    LayoutInflater.from(viewGroup.getContext()),
    R.layout.list_item,
    viewGroup,
    false);

6 属性Setters

有了Data Binding,即使属性没有在declare-styleable
中定义,我们也可以通过 xml 进行赋值操作。 为了演示这个功能,自定义 View -NameCard,属性资源R.styleable.NameCard中只定义了一个age属性,其中firstNamelastName只有对应的两个setter方法。
只要有setter方法就可以像下面代码一样赋值:

<com.liangfeizc.databindingsamples.attributesetters.UserView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="@dimen/largePadding"
    app:onClickListener="@{activity.clickListener}"
    app:firstName="@{@string/firstName}"
    app:lastName="@{@string/lastName}"
    app:age="27" />

7.转换器 (Converters) ----->ConversionsActivity.java

使用Converter一定要保证它不会影响到其他的属性,例如这个@BindingConversion
-convertColorToString就会影响到android:visibility, 因为他们都是都符合从 int 到 int 的转换。

在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过DataBinding可以进行转换。
例如,下面代码中如果要为属性android:background赋值一个int型的 color 变量:

<View
  android:background="@{isError.get() ? @color/red : @color/white}"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  app:layout_height="@{height}" />

只需要定义一个标记了 @BindingConversion 的静态方法即可(方法的定义位置可以随意):

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

8.绑定事件处理方法

9. BindingAdapter的使用场景

10.双向绑定

用法举例

很简单,在要使用双向绑定的地方,使用 “@={}” 即可。

<EditText android:text="@={user.firstName}" />

firstName 必须是 ObservableField <T> 类型

适用范围

双向绑定只适用于那些某个属性绑定监听事件的控件,如

  • TextView/EditView/Button (android:text, TextWatcher)
  • CheckBox (android:checked, OnCheckedChangeListener)
  • DatePicker(android:year, android:month, android:day, OnDateChangedListener)
  • TimePicker(android:hour, android:minute, OnTimeChangedListener)
  • RatingBar(android:rating, OnRatingBarChangeListener)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容