Data Binding Library (数据绑定库)简介和使用

本来想把DataBindingLibrary理解完滴,但是由于初学这个,有的也不是很清楚,没有理解的地方,也就是将官网给翻译了一下,高手勿喷。

Data Binding Library是一个系统支持库,使用声明式布局,并尽量减少绑定应用程序逻辑和布局的中间代码,可以在Android 2.1 (API级别7+)之后的所有android平台使用它;要使用数据绑定功能,Gradle版本插件必须是1.5.0-alpha1或更高的版本;

使用配置环境:
在module的build.gradle中配置下列代码:

android {
    .....
    .....
    dataBinding{
      enabled = true 
    }
}
如果应用依赖的使用DataBindingLibrary的库,则程序模块也必须进行上面的配置

简单使用

1、定义布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.sl.databindingdemo.bean.User"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <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>

根元素使用layout标签,data为数据元素,后面再跟布局元素,data中的变量variable描述了一个可能在布局中使用的属性,布局表达式中使用 @{} 语法写入属性参数中,如android:text="@{user.firstName}"
2、定义User对象:

一种情况的User:
package com.sl.databindingdemo.bean;
public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}
第二种情况的User
package com.sl.databindingdemo.bean;
public class User {
    private String firstName ;
    private String lastName ;

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

从数据绑定的角度看,这两个类是等价的。用于TextView的android:text属性的“@{user.firstName}”表达式将访问前一个类中的firstName字段和后一个类中的getFirstName()方法,或者,如果firstName()方法存在,也将被解析;
3、数据绑定

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

表达式语言

共同特征
表达式语言看起来像Java中的表达式,可以在表达式语言中使用一下的操作符合关键字:

  • 数学上的:+ - * / % (加减乘除余)
  • 字符串连接 : +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元 + - !~
  • 位运算 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 分组 ()
  • 文字:字符、字符串、数字、null
  • 强转
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元操作符 ?:
    例:
<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(index + 1)}"
            android:visibility="@{ user.age < 18 ? View.VISIBLE : View.GONE}"
            android:transitionName='@{"image_" + id }'/>

缺少的操作

有些你在Java中能使用的表达式语法子这里会缺少下面的一些操作符:

  • this
  • super
  • new
  • 明确的泛型调用

空合并运算符

空合并操作符 (??):如果左操作数不为空,则选择左操作数;如果右操作数为空,则选择右操作数

android:text="@{user.displayName ?? user.lastName}"

在功能上等同于:

android:text= "@{user.displayName != null ? user.displayName : user.lastName}"

属性引用

表达式可以使用以下格式引用类中的属性,这对于字段、getter和ObservableField对象是一样的

android:text= "@{user.lastName}"

避免空指针异常

生成的数据绑定代码自动检查空值,并避免空指针异常。例如,在表达式@{user.name}中,如果user为空,则为user.name分配其默认值null。如果你引用用户。age,其中age类型为int,然后数据绑定使用默认值0。

集合

常见的集合:数组,列表,稀疏列表(sparse lists),和映射集合(map),为了方便访问可以使用[]操作符编译,在编译时variable中不支持“<>”时,需要使用转义的符号:&lt; &gt;,转义字符对照表可查看:http://tool.oschina.net/commons?type=2

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

注意:您还可以使用 Object.key ,例上面的@{map[key]}可以替换为@{map.key}

字符串文字

你可以使用单引号包围属性值,这样可以在表达式之内使用双引号,例:

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

也可以使用双引号包围属性值,但是里面的表达式的需使用单引号,例:

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

资源(Resources)

可以使用一下语法访问表达式中的资源:

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

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

<string name="nameFormat">%s, %s</string>

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

当一个复数有多个参数时,应该传递所有的参数:

 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

方法引用

事件可以直接绑定到处理方法,类似在xml布局中是调用的android:onClick可以分配给Activity中的方法一样。与View#onClick属性相比,一个主要的优点是表达式在编译时被处理,所以如果方法不存在或者他的签名不正确,会收到错误的编译信息;
方法引用和监听绑定器的主要区别在于实际的监听器实现是在绑定数据时创建的,而不是在事件触发时创建的 ;
要将事件分配给其处理程序,使用常规的绑定表达式,其值是要调用的方法名称。例如:数据的对象有两个方法:

package com.sl.databindingdemo.method;
import android.util.Log;
import android.view.View;
public class MyHandles {
    public void onClickFriend(View view){
        Log.e("MyHandles","我被点了");
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.sl.databindingdemo.bean.User"/>
        <variable name="handlers"
            type="com.sl.databindingdemo.method.MyHandles"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:text="@{user.firstName}"
            android:onClick="@{handlers::onClickFriend}"/>  //---> 注意为 handles::onClickFriend绑定方法
    </LinearLayout>
</layout>

在程序中也需要将对象绑定给布局

ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        final User user = new User("Test","User");
        binding.setUser(user);
        binding.setHandlers(new MyHandles());

注意:表达式中的方法的签名必须与监听器中的对象的方法的签名完全匹配


监听器绑定

监听器绑定是事件发生时运行的绑定表达式。他类似于方法引用,但是他们允许您运行任意数据的绑定表达式;在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有你的返回值比需与监听器的期望返回值相匹配(除非预期为Void);例:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.sl.databindingdemo.bean.User"/>
        <variable
            name="presenter"
            type="com.sl.databindingdemo.method.Presenter"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"
            android:onClick="@{ ()->presenter.onSaveClick(user)}"/>
    </LinearLayout>
</layout>
package com.sl.databindingdemo.method;
import android.util.Log;
import com.sl.databindingdemo.bean.User;
public class Presenter {
    public void onSaveClick(User user){
        Log.e("监听器绑定", "onSaveClick:" );
    }
}

ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        final User user = new User("Test","User");
        binding.setUser(user);
        binding.setPresenter(new Presenter());

监听器由仅允许作为表达式的根元素的lambda表达式表示。在表达式中使用回调函数时,数据绑定会自动为事件创建必要的监听器和注册表。当视图触发事件时,数据绑定将计算给定的表达式,就像常规的绑定表达式一样,当这些监听器表达式被计算时,你仍然可以获得null和数据绑定的线程安全性;
上面的表达式可以写成:

android:onClick="@{ (view)->presenter.onSaveClick(user)}"

如果想使用表达式中的参数,他也可以按如下方式工作:

android:onClick="@{ (theView) -> presenter.onSaveClick(theView,user)}"

public class Presenter {
    public void onSaveClick(View view,User user){
        Log.e("监听器绑定",((TextView)view).getText().toString());
        Log.e("监听器绑定", "onSaveClick:" );
    }
}

也可以使用多个参数的lambda表达式:

public class Presenter {
    private static final String TAG = "Presenter";
    public void onCompletedChanged(User user,boolean isChecked){
        Log.e(TAG, "onCompletedChanged: "+ user.getFirstName() + "  " + user.getLastName() );
        Log.e(TAG, "onCompletedChanged: " + isChecked);
    }
}

//布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.sl.databindingdemo.bean.User"/>
        <variable
            name="presenter"
            type="com.sl.databindingdemo.method.Presenter"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onCheckedChanged="@{(cb,isChecked)-> presenter.onCompletedChanged(user,isChecked)}"            />
        <ListView
            android:id="@+id/mListView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

 ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        final User user = new User("Test","User");
        binding.setUser(user);
        binding.setPresenter(new Presenter());

如果正在监听的事件返回一个其类型不是void,则表达式必须返回相同类型的值。如长按事件,返回值为boolean:

public class Presenter {
    private static final String TAG = "Presenter";
    public boolean onLongClick(View view,User user){
        Log.e(TAG, "onLongClick" );
        return false ;
    }
}

android:onLongClick="@{(theView)->presenter.onLongClick(theView,user)}"

如果由于null对象导致无法计算表达式,数据绑定将返回该类型的默认Java值,如null用于引用类型,0用于int类型;
如果需要使用谓词(例如三元表达式),则可以将void用作符号:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

布局细节

导入

  • import元素中可以使用零个或多个data元素,这些就想是在Java中一样可以轻松的引用布局中的类。
<data>
        <import type="android.view.View"/>
        <import type="android.text.TextUtils"/>
</data>

使用View

     <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"
            android:visibility="@{ TextUtils.isEmpty(user.firstName) ? View.VISIBLE : View.GONE}"
            />

当类名有冲突时,应该为其中一个重命名一个别名:alias:例

<data>
        <import type="android.view.View"/>
        <import type="com.sl.databindingdemo.method.View"
            alias="Vista"/>
</data>
  • 导入的类型可以用作变量和表达式的类型引用,下面示例显示了用作变量类型的User和List:
    <data>
        <import type="com.sl.databindingdemo.bean.User"/>
        <import type="java.util.List"/>
        <variable
            name="user"
            type="User"/>
        <variable
            name="userList"
            type="List<User>"/>
    </data>

注意:Android studio 还不能处理导入,所以导入变量的自动补全可能无法子啊IDE中工作。您的应用程序仍然在编译,您可以通过在变量定义中使用完全限定名来解决IDE的问题;

  • 当在表达式中应用静态字段和方法时,也可以使用导入的类型:
public class MyStringUtils {
    public static  String capitalize(String str){
        return "想咋咋滴" ;
    }
}

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

Variables(变量)

你可以在data元素中使用多个的Variable(变量)元素,每个variable元素描述一个属性,该属性可以设置在布局上,用于布局文件中的绑定表达式;下面的实例声明user、image、和note变量:

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

变量类型会在编译时进行检查,因此,如果以个变量实现了 Observable 或者是一个 Observable集合,那么应该在类型中反映出来,如果变量没有实现可观察接口的基类或接口,则不会观察到变量;
当多样化的配置(如横向、纵向)有不同的布局文件时,变量就会组合在一起,这些布局文件之间不应该有冲突的变量定义;
生成的绑定类为每个描述的变量都有一个setter和getter,变量接受最初的默认值直到setter被调用,引用被称为null类型,0为int,false为boolean值等;
一个特殊的变量名 context 生成用于绑定表达式,context的值是根据根视图getContext()方法获取的Context对象,context变量被具有该名称的此案是变量声明覆盖。

Includes

通过使用应用程序名称控件和属性中的变量名,可以将变量从包含的布局的布局中传递到includ布局的绑定中;

<?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.sl.databindingdemo.bean.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>

这里在name.xml和contact.xml布局中都必须有一个user变量
数据绑定不支持将include作为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.sl.databindingdemo.bean.User"/>
   </data>
   <Merge>
       <include layout="@layout/name"
                     bind:user="@{user}"/>
       <include layout="@layout/contact"
                     bind:user="@{user}"/>
   </Merge>
</layout>




使用 Observable 数据对象

任何的普通的Java对象都可以用于数据绑定,但是修改对象不会自动导致UI更行。数据绑定可用于让数据对象能够在数据更改时通知其他对象(称为侦听器)。有三中不同类型的数据更改通知机制:Observable 对象Observable 字段Observable 集合
当这些Observable数据对象绑定到UI且数据对象属性进行更改时,UI也将被自动更新;

Observable Field

创建实现Observable接口的类,如果这个类只有一些属性,那么这个实现就有点多余了,可以是使用泛型 Observable类和以下特定原始的类来使字段可被观察:

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

要访问字段的值,需要使用set()和get()方法,例:

mUserInfo.firstName.set("Google");
int age = mUserInfo.age.get();

注:Android Studio 3.1和更高版本允许您用LiveData对象替换可观察字段,这为您的应用程序提供了额外的好处,更多信息 Use LiveData to notify the UI about data changes

Observable collections (Observable 集合)

一些应用程序使用动态结构来保存数据,Observable collections 允许使用对这些对象使用键存取。

  • 当键是String等引用类型时ObserableArrayMap类非常有用:
        ObservableArrayMap<String,Object> user = new ObservableArrayMap<>();
        user.put("firstName","Google");
        user.put("lastName","Inc.");
        user.put("age",18);

在布局中可以直接使用String key来访问:

<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"/>
  • 当键是Integer类型时,ObserbableArrayList非常有用:
        ObservableArrayList<Object> user = new ObservableArrayList<>();
        user.add("Google");
        user.add("Inc.");
        user.add(18);

在布局中,可以使用索引列表访问,com.example.my.app.Fields应该是一个静态类,里面的值代表下标值,如下所示:

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

Obsevable Object

实现 Observable接口的的类允许注册监听器,这些监听器将监听Observable对象上的属性改变时得到通知;
Observable 接你具有添加和删除监听器的机制,但是开发者必须决定什么时候发送通知,为了简化开发,Databinding提供了BaseObservable类,他是了监听器的注册机制,实现BaseObservable的数据类负责在属性发生变化时通知。这是通过为getter分配一个可绑定的注释并在setter中调用notifyPropertyChanged()方法完成滴,如下所示:

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        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);
    }
}

数据绑定在模块包中生成一个名为BR的类,其中包含用于数据绑定的资源的id。可绑定注释在编译期间在BR类文件中生成一个条目。如果数据类的基类不能更改,那么可以使用PropertyChangeRegistry对象实现可观察接口,以便有效地注册和通知侦听器;





生成绑定的类

DataBinding Library生成用于访问布局的变量和绑定视图的绑定类。下面讲解如何如何创建和定制生成的绑定的类;
生成的绑定类将布局变量与布局中的视图链接在一起。绑定类的名称和包可以自定义。所有生成的类都继承自ViewDataBinding类;
为每个布局文件生成一个绑定类。默认情况下,类的名称基于布局文件中的名称,将其转换为Pascal大小写并向其添加后缀。如布局文件名为:activity_main.xml,相应的生成的类是ActivityMainBinding,该类保存从布局属性(例如:user variable)到布局视图的所有绑定,并知道如何为绑定表达式赋值;

创建一个bingding对象

绑定对象应该在布局填充后立即创建,以确保视图层次结构在绑定到具有布局中的表达式的视图之前没有被修改。将对象绑定到布局的最常见方法是在绑定类上使用静态方法。可以使用绑定类的inflate()方法填充视图层次结构并将对象绑定到它,如下例所示

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
}

除了LayoutInflater对象之外,还有另一个版本的inflate()方法,它接受ViewGroup对象,如下例所示:

MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);

如果布局使用不同的机制填充,则可以分别绑定,如下所示:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时候绑定类型不能预先知道。在这种情况下,可以使用DataBindingUtil类创建绑定,如下面的代码片段所示

View rootView = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);

如果您在Fragment、ListView或RecyclerView适配器中使用数据绑定项,您可能更喜欢使用绑定类或DataBindingUtil类的inflate()方法,如下面的代码示例所示:

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

带有ID的视图

数据绑定库为布局中有ID的每个视图在绑定类中创建一个不可变字段。例如,数据绑定库从以下布局创建TextView类型的firstName和lastName字段:

<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}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

库通过一次遍历从视图层次结构中提取包括id在内的视图。这种机制比为布局中的每个视图调用findViewById()方法更快.
ID在没有数据绑定的情况下没有那么必要,但是仍然有一些实例需要从代码访问视图

Variables

数据绑定库为布局中声明的每个变量生成访问器方法。例如,下面的布局在绑定类中为用户、图像和注释变量生成setter和getter方法:

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

ViewStub

ViewStub和普通的视图有些不同。它们一开始是不可见的,当他们变得可见或者被明确告知需要填充时,它们会填充另一个布局来取代自己的布局。

由于ViewStub本质上在View的层级上是消失的,所以绑定对象中的视图也必须消失以允许收集。由于视图(View)是final类型的,所以用一个ViewStubProxy来代替了ViewStub,让开发者在ViewStub存在的时候可以访问它,并且在ViewStub填充的时候也可以访问被填充的视图层级。

在填充另一个布局的时候,必须为新的布局建立一个绑定。因此,ViewStubProxy必须监听ViewStubViewStub.OnInflateListener并且在那个时候建立绑定。由于只能存在一个ViewStub.OnInflateListener,所以ViewStubProxy允许开发者对它设置一个OnInflateListener,这样就会在建立绑定后调用这个监听。

高级绑定

动态变量

有时,不知道特定的绑定类。例如,一个RecyclerView.Adapter对任意布局进行操作并不知道具体的绑定类,他仍然必须在调用OnBindViewHolder()方法期间分配绑定值;
在下面的实例中,RecyclerView绑定的所有布局都有一个 item变量。BindingHolder对象有个一个返回 ViewDataBinding基类的getBinding方法:

public void onBindViewHolder(BindingHolder holder, int position) {
    final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
}

注:DataBindingLibrary在模块包中生成了一个名为BR的类,其中包含用于绑定的资源的id。在上面的例子中,库自动生成BR.item变量;

后台线程

你可以在后台线程中更改数据模型,只要他不是一个集合。数据绑定在评估期间本地化每个变量/字段,已避免发生任何并发问题;

自定义绑定类名

默认情况下,绑定类基于布局文件的名称生成,以大写字母开头,删除下划线(_),将下面的字母大写,并为单词binding添加为后缀。类放在模块包下的数据绑定包中。例如,布局文件contact_item.xml生成ContactItemBinding类。如果模块包是com.example.my.app,然后绑定类被放在com.example.my.app.databinging包中。
绑定类可以通过调整数据元素的类属性重新命名或放在不同的包中。例如,下面的布局在当前模块中的databinding包中生成ContactItem绑定类:

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

通过在类名前面加上 .,可以在另一个包中生成绑定类。下面的示例在模块包中生成绑定类:

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

您还可以在希望生成绑定类的地方使用完整的包名。下面的示例在com.example中创建ContactItem绑定类:

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




Binding adapters

绑定适配器负责进行适当的框架来设置值。例如设置属性值调用 setText()方法,设置事件调用 setOnClickListener() 方法;
DataBingdingLibaray允许您指定用于设置值的方法,提供您自己的绑定逻辑,并通过调用适配器指定放返回对象的类型。

设置属性值(Setting attribute values)

当绑定值发生变化时,生成的绑定类必须使用绑定表达式调用视图傻瓜的setter方法,您可以运行DataBindingLibrary自动确定方法、显示声明的方法或者提供自动以逻辑来选择方法;

自动选择方法(Automatic Method selection)

对于名为example的属性,库会自动尝试查找接受兼容类型作为参数的方法setExample(arg)。不考虑属性的名称空间,搜索方法时只使用属性名和类型;

例如,给定android:text="@{user.name}"表达式,库查找接受user.getName()返回的类型的setText(arg)方法。如果user.getName()的返回类型是String,库将查找接受字符串参数的setText()方法。如果表达式返回int,库将搜索接受int参数的setText()方法。表达式必须返回正确的类型,如果需要,可以强制转换返回值;

即使给定名称中不存在属性,数据绑定也可以工作。然后,您可以使用数据绑定为任何setter创建属性。例如,support类DrawerLayout没有任何属性,但是有很多setter。下面的布局自动使用setScrimColor(int)和setDrawerListener(DrawerListener)方法分别作为应用程序的setter:scrimColor和app: DrawerListener属性

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

指定自定义方法名称(Specify a custom method name)

有些属性具有名称不匹配的setter。在这些情况下,可以使用BindingMethods注释将属性与setter关联。注释与类一起使用,可以包含多个BindingMethod注释,每个重命名的方法都有一个注释。绑定方法是可以添加到应用程序中的任何类的注释。在下面的示例中,android:tint属性与setImageTintList(ColorStateList)方法相关联,而不是与setTint()方法相关联:

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

大多数情况下,您不需要在Android框架类中重命名setter。属性已经使用名称约定来实现,以便自动查找匹配方法

提供自定义逻辑(Provide custom logic)些属性需要自定义绑定逻辑。例如,android:paddingLeft属性没有关联setter。相反,提供了setPadding(左、上、右、下)方法。使用BindingAdapter注释的静态绑定适配器方法允许您定制如何调用属性的setter;

Android framework类的属性已经创建了BindingAdapter注释。例如,下面的示例显示了paddingLeft属性的绑定适配器:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view.setPadding(padding,
                  view.getPaddingTop(),
                  view.getPaddingRight(),
                  view.getPaddingBottom());
}

参数类型是重要的。 第一个参数决定的类型 视图相关联的属性。 第二个参数决定 接受类型在给定属性的绑定表达式。

对于其他类型的定制绑定适配器有用。 例如,一个 自定义加载程序可以从一个工作线程调用来加载一个图像。

绑定适配器,您提供的定义覆盖默认的适配器 Android框架当有冲突。

你也可以让适配器接收多个属性,如图所示 下面的例子:

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.get().load(url).error(error).into(view);
}

您可以使用适配器在你的布局,如以下示例所示。 请注意 @drawable/venueError是指资源在你的应用,围绕着 资源与@{}是一个有效的绑定表达式:

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

注:数据绑定库忽略了自定义名称空间匹配的目的

适配器就是如果两个imageUrlerror用于一个ImageView对象和imageUrl是一个字符串,error是一个Drawable。 如果你想要适配器 当任何属性的设置,可以设置可选的requireAll标志的适配器false,如以下示例所示:

@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
  if (url == null) {
    imageView.setImageDrawable(placeholder);
  } else {
    MyImageLoader.loadInto(imageView, url, placeholder);
  }
}

注意:你绑定适配器覆盖默认的数据绑定适配器时 有一个冲突.

绑定适配器方法可以选择把旧值的处理程序, 一个 方法采取新旧值应该申报的东西所有旧的值 属性第一,紧随其后的是新值,如下面的示例所示:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
  if (oldPadding != newPadding) {
      view.setPadding(newPadding,
                      view.getPaddingTop(),
                      view.getPaddingRight(),
                      view.getPaddingBottom());
   }
}

事件处理程序只能使用接口或抽象类 抽象方法,如以下示例所示:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    if (oldValue != null) {
      view.removeOnLayoutChangeListener(oldValue);
    }
    if (newValue != null) {
      view.addOnLayoutChangeListener(newValue);
    }
  }
}

使用此事件处理程序在你的布局如下:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

当一个listener有多个方法,它必须分成多个侦听器。 例如,View.OnAttachStateChangeListener有两个 方法:onViewAttachedToWindow(View)onViewDetachedFromWindow(View), 库提供两个接口来区分和处理程序的属性 他们:

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
  void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
  void onViewAttachedToWindow(View v);
}

因为改变一个侦听器也能影响到另一个,你需要一个适配器 针对属性或两者兼而有之。 你可以设置requireAllfalse注释中指定并不是每个属性必须 分配一个绑定表达式,如以下示例所示

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }

        OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener,
                R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

上面的例子比正常情况稍微复杂,因为视图在监听器中使用add和remove来代替View.OnAttachStateChangeListener中的set的方法。android.databinding.adapters.ListenerUtil类可以帮助跟踪以前的监听器,让他们可以在绑定Adaper时被移除。

通过给OnViewDetachedFromWindowOnViewAttachedToWindow接口添加@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注解,数据绑定代码生成器就知道只在Honeycomb MR1和新设备生成监听器,由addOnAttachStateChangeListener(View.OnAttachStateChangeListener)支持相同的版本。

对象转换(Object conversions)

自动对象转换(Automatic object conversion)

当从绑定表达式返回Object时,库选择用于设置属性值的方法。Object被转换为所选方法的参数类型。这种行为在使用ObservableMap类存储数据的应用程序中非常方便,如下面的例子所示

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

您还可以使用对象引用映射中的Object.key的符号。例如,上面示例中的@{userMap["lastName"]}可以替换为@{userMap.lastName}

表达式中的userMap对象返回一个值,该值自动转换为setText(CharSequence)方法中用于设置android:text属性值的参数类型。如果参数类型不明确,则必须在表达式中强制转换返回类型;

自定义转换(Custom vonversions)

在某些情况下,特定类型之间需要自定义转换。例如,视图的android:background属性预期为Drawable,但是指定的颜色值是Integer。下面的示例显示了一个需要Drawable的属性,但是提供了一个Integer:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

无论何时,只要需要一个Drawable,并且返回一个整数,int都应该转换为ColorDrawable。转换可以使用带有BindingConversion注释的静态方法完成,如下所示:

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

但是,绑定表达式中提供的值类型必须一致。不能在同一个表达式中使用不同的类型,如下面的示例所示:

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • 导航: 搭建环境 数据绑定编译器V2 数据绑定布局文件编写你的第一套数据绑定表达式数据对象绑定数据事件处理方法参考...
    yyg阅读 316评论 0 0
  • Hello,大家好我是没有蜡不同于已经工作了的苏苏我还是个穷!学!生!!所以我只能在很有限的基础上进行投资理财但是...
    苏苏不差钱阅读 940评论 2 2
  • 我爱徒步的感觉,汗水跟我一起,走个俩小时山路,顿觉眼前一亮,脑海中出现了一只可爱的小猴子,像黄蓉。 突然想起普洱,...
    立七阅读 551评论 0 4