2.2.1.Architecture components_data binding

参考

https://developer.android.com/topic/libraries/data-binding

https://blog.csdn.net/qiang_xi/article/details/74347880

配置

l 设备需要是Android4.0 api14及以上版本,

l 项目根目录的build.gradle中的classpath 'com.android.tools.build:gradle:xxx' 版本需要1.5.0及以上。

l 在module的build.gradle中

android {
    dataBinding {
        enabled = true
}
}

注意1:即使app module不直接使用data binding,如果app module依赖的库使用了 data binding,那么app module也必须配置。

注意2:Arrays and a generic type, such as the Observable class, might incorrectly display errors.

注意3:不用添加依赖,添加完上边的后就会直接引入几个库:

wps64

布局和绑定表达式

你可以在布局文件中写入 布局 和 绑定表达式,如下所示:

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

绑定布局和普通的布局有些许不同,

不同之处在:绑定布局根元素是layout,在layout内部接着data元素和布局。

  • l data元素:就是用来声明一些绑定参数,例如要绑定的bean。
  • l 布局:就是普通布局中的布局,只不过在具体属性的设置用@{bean.xxx}来赋值
<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

另外:

  • l data binding库会自动生成将布局中的视图与数据对象绑定所需的类。
  • l 布局表达式应保持小而简单,因为不能测试、ide对此的支持是有限的。

数据类的定义规则

public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}
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;
  }
}

这两种定义方式都是可以的,

在布局中只有一种写法android:text="@{user.firstName}",但在查找对应的值时,

  • l 会去查找变量名对应的字段,比如firstName
  • l 也可以找此字段的get方法,比如getFirstName();
  • l 或者此变量名 有对应的方法也可以,比如firstName();
  • l 当然不一定要用final修饰,只不过User不是一个可观察的对象,所以user数据发生变化并会不通知相关ui进行更新。

Binding data

上边定义了一个布局和一个数据类,那么想要显示对应的ui肯定还是需要进行 数据对象的创建 及 和布局的绑定设置的。

系统为每个绑定布局文件生成一个绑定类。 默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。 上面的布局文件名是activity_main.xml,因此相应的生成类是ActivityMainBinding。 此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式分配值。

创建绑定的推荐方法是在inflating 布局的同时进行绑定,例如 如以下示例所示:

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

或者,您可以使用LayoutInflater获取绑定对象,如以下示例所示:

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

如果你是在Fragment、ListViewAdapter、RecyclerViewAdapter等中使用data binding的话,可以使用下边方法:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);

// or

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

表达式语法

操作符

The expression language looks a lot like expressions found in managed code.

You can use the following operators and keywords in the expression language:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <= (Note that < needs to be escaped as &lt;)
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:

Examples:

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

default

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.name,default=1}" />

default只在layout编辑器中有效,效果和tools:text=”1”一样。

不能使用的操作

以下操作是不能使用的:

  • l this
  • l super
  • l new
  • l Explicit generic invocation

空合并运算符??

空合并运算符??

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

如果不是null则取前边的,否则取后边的,等效于下边:

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

属性引用

表达式可以使用以下格式来引用类中的属性,以下格式对于字段,getter和ObservableField对象是相同的:

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

自动避免空指针

生成的databinding代码会自动的检查null值,来避免空指针。

例如, @{user.name},如果user为null,那么最终就取name的默认值,即String的默认值,即null,

如果@{user.age},则会取age的默认值,即int的默认值,为0;

集合

arrays, lists, sparse lists, and maps这些集合都可以通过[ ]来访问。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;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]}"

注意:要使XML语法正确,必须转义 < 。例如:您必须编写List<;String>,而不是List<String>。

你也可以使用@{map.key}等效于@{map[key]}

引号和字符串常量

你可以使用,单引号在外边,双引号在里边:

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

你也可以使用双引号在外边,back quotes(键盘左上角的字符`)在里边:

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

Resources

You can access resources in an expression by using the following syntax:

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

也可以使用格式化字符串:

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

nameFormat为字串的id名后边括号里的是具体的实参。

当然也可以引用strings.xml中的:

android:text="@{@string/format_test(@string/activity_lifecycle)}"

一些资源需要显式类型求值,如下表所示:

Type

Normal reference

Expression reference

String[]

@array

@stringArray[index]

int[]

@array

@intArray[index]

TypedArray

@array

@typedArray

Animator

@animator

@animator

StateListAnimator

@animator

@stateListAnimator

color int

@color

@color

ColorStateList

@color

@colorStateList

事件处理

data binding允许你写表达式来处理那些从view分发的事件,比如onClick事件,

事件属性名称由侦听器方法的名称确定,但有几个例外。

例如,View.OnClickListener有一个 onClick(View)方法,因此在布局中的属性就叫做 android:onClick。

你可以使用两种方式处理事件:

  • l 方法引用
  • l 监听器绑定

方法引用

java8双引号::

在表达式中,可以引用符合侦听器方法签名的方法。

事件可以直接绑定到处理程序方法,类似于可以将android:onClick分配给Activity中的方法的方式。 但和View onClick属性相比,一个主要优点是表达式是在编译时处理的,因此,如果该方法不存在或其签名不正确时,则会收到编译时错误。

方法引用和侦听器绑定之间的主要区别在于,方法引用的 实际的侦听器实现是在绑定数据时创建的,而不是在事件触发时创建的。 如果您希望在事件发生时执行表达式,则应使用侦听器绑定。

在创建处理器方法时,方法的参数必须和要和要监听的事件一致,比如下边监听的是onClick,View.OnClickListener有一个 onClick(View)方法,那么在创建事件处理器时就需要创建的方法参数中也要带view:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <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:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

监听器绑定

java8的lambda表达式

侦听器绑定是事件发生时运行的绑定表达式。 它们类似于方法引用,但是它们使您可以运行任意数据绑定表达式。 适用于Gradle 2.0版及更高版本的Android Gradle插件提供了此功能。

onClick

  • l 在上边方法引用中,方法的参数必须与事件侦听器的参数匹配。
  • l 而在侦听器绑定中,只有您的返回值必须与侦听器的期望返回值匹配(除非期望返回void)。 例如,假设以下具有onSaveClick()方法的presenter类:
public class Presenter {
    public void onSaveClick(Task task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>
  • l 在表达式中使用回调时,数据绑定会自动为事件创建并注册必要的监听器。当视图触发事件时,数据绑定会对给定表达式求值。
  • l 与常规绑定表达式一样,在对这些监听器表达式求值时,仍会获得数据绑定的 Null 值和线程安全。

在上面的示例中,我们尚未定义传递给onClick(View)的view参数。 侦听器绑定为侦听器参数提供了两种选择:您可以忽略该方法的所有参数,也可以全部命名。 如果您想命名参数,则可以在表达式中使用它们。 例如,上面的表达式可以编写如下:

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

或者,如果要在表达式中使用参数,则可以按以下方式工作:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

多个参数的lambda表达式

您可以使用具有多个参数的lambda表达式:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox 
android:layout_width="wrap_content" 
android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

返回值不是void

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果由于null 对象而无法对表达式求值,则数据绑定将返回该类型的默认值。 例如,对于引用类型,为null,对于int为0,对于布尔值为false,等等。

其他

  • l 如果需要将表达式与谓词(例如,三元)一起使用,则可以使用void作为符号。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
  • l 尽量不要把业务逻辑都写在布局文件中,应该保持布局文件的简洁,而把具体的复杂的业务逻辑写在具体的类的方法中。

import, variable, include,ViewStub

import

  • l import类似于java代码中的import,
  • l 默认导入java.lang下的一些包

DataBinding已经默认帮我们导入了String类,Integer类等java.lang下的基本数据类型

  • l import的类可以用在布局中,也可以用在variable
<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}"/>

导入的类还可以用于强转

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

导入的类还可以使用其静态常量和静态方法

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

类型别名

如果导入的类名有重复的,则可以进行别名来避免混淆:

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

variable

<data>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
</data>
  • l 当针对各种配置(例如,横向或纵向)有不同的布局文件时,将合并变量。 这些布局文件之间不得有冲突的变量定义。
  • l 对于每个所描述的变量,生成的绑定类都有一个setter和getter。 变量将使用默认的托管代码值,直到调用setter为止,引用类型为null,int为0,布尔值为false,等等。
  • l databinding会自动导入Context对象,context的值是根视图的getContext()方法获取的,所以我们可以在布局中直接使用context,而不用导入或声明。

但是如果我们自己通过variable标签定义了一个name为context的对象,那么会覆盖掉系统提供的context。

include

数据传递

include类似于普通布局中使用的include,但有一点不同是,

如果include的也是一个绑定布局,并且有一个参数的类型和第一个布局一样,那么我们把参数传递进去,只需要把实际数据设置给第一个绑定类,那么include进来的布局也会显示对应的数据。

  • !ViewStub也支持!
  • 另外,include不支持自定义BindingMethods,BindingAdapter。

例子

activity_main2.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

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

        <variable
            name="index"
            type="int" />

        <variable
            name="handler"
            type="com.example.jetpackdemo.databingding.MyHandlers" />

    </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="@{@string/test(index)}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=1}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.sex}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{(view)->handler.onClickFriend(view)}"
            android:text="Hello World!" />

        <include
            app:user2="@{user}"
            layout="@layout/activity_main3" />
    </LinearLayout>
</layout>

app:user2="@{user}"中的user2就是include的布局中定义的参数名,user是此布局定义的参数名。

activity_main3.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="user2"
            type="com.example.jetpackdemo.databingding.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="@{user2.name,default=1}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user2.age)}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user2.sex}" />

    </LinearLayout>
</layout>
ActivityMain2Binding activityMain2Binding = DataBindingUtil.setContentView(this, R.layout.activity_main2);
User user = new User("haha", 18, "女");
activityMain2Binding.setUser(user);

最终我们只需要把数据设置给ActivityMain2Binding ,那么这两个xml都会显示对应的数据。

wps65

include上加上id

还用上边例子,如果在include上加上id,那么就可以直接访问include的绑定布局对象,而不需要再去用DataBingdingUtil去获取。

<include
    android:id="@+id/databinding2"
    layout="@layout/databinding2"
    app:user2="@{user}" />
databinding1Binding.databinding2

分析一下生成的代码就明白了:

Databinding1BindingImpl继承自Databinding1Binding

public Databinding1BindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
    this(bindingComponent, root, mapBindings(bindingComponent, root, 9, sIncludes, sViewsWithIds));
}
private Databinding1BindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
    super(bindingComponent, root, 2
        , (android.widget.Button) bindings[1]
        , (com.example.jetpackdemo.databinding.Databinding2Binding) bindings[8]
        , (android.widget.LinearLayout) bindings[0]
        , (android.widget.TextView) bindings[5]
        , (android.widget.TextView) bindings[4]
        , (android.widget.TextView) bindings[6]
        );

Databinding1Binding是绑定布局生成的绑定类

protected Databinding1Binding(Object _bindingComponent, View _root, int _localFieldCount,
    Button btRefresh, Databinding2Binding databinding2, LinearLayout root, TextView tvAge,
    TextView tvName, TextView tvSex) {
  super(_bindingComponent, _root, _localFieldCount);
  this.btRefresh = btRefresh;
  this.databinding2 = databinding2;
  setContainedBinding(this.databinding2);
  this.root = root;
  this.tvAge = tvAge;
  this.tvName = tvName;
  this.tvSex = tvSex;
}

调用构造函数时,传递的root view布局肯定是inflate过的,也就是说布局的层级已经有了。

可以看到对在构造函数中直接对Databinding2Binding 进行了赋值,那么这个对象是从哪创建的,

关键就在mapBindings这个方法,这个方法就是解析view及其子view上设置的tag(这个tag是自动添加上的),并把解析出来的对象放到bindings数组的合适的位置。

其中就会调用DataBindingUtil.bind方法(不管include的布局是否是绑定布局),如果include的布局是绑定布局,那么就会获得绑定对象。

ViewStub

在绑定布局中使用ViewStub,会在生成的绑定类中用ViewStubProxy来代替ViewStub,

因为ViewStub本质上是个不可见的view,所以ViewStub引用的布局会在ViewStub inflate后才生成绑定对象。

注意:

  • ViewStub也支持 数据传递

只是需要注意的是,是在ViewStub inflate之后才支持,也就是说需要在inflate之后才可以设置要传递的值。

  • ViewStub不支持自定义BindingMethods,BindingAdapter

和可观察的对象一起使用

可观测性是指对象通知其他人其数据变化的能力。 数据绑定库 使您可以观察对象,字段或集合。

任何bean数据对象都可以用于数据绑定,但是修改对象不会自动导致UI更新。 数据绑定可用于使您的数据对象在数据更改时通知其他对象(称为侦听器)。

有三种不同类型的可观察类:对象object,字段field 和集合collection。

当这些可观察数据对象之一绑定到UI且数据对象的属性更改时,UI将自动更新。

Observable fields

注意:Android Studio 3.1及更高版本允许您用LiveData对象替换Observable fields,这为您的应用程序提供了更多好处。

下边是一些基本数据类型对应的Observable类,

  • l ObservableBoolean
  • l ObservableByte
  • l ObservableChar
  • l ObservableShort
  • l ObservableInt
  • l ObservableLong
  • l ObservableFloat
  • l ObservableDouble
  • l ObservableParcelable

可观察字段是具有单个字段的自包含可观察对象。 原始版本避免在访问操作期间装箱和拆箱。 要使用此机制,请使用Java编程语言创建一个public final属性,或者使用Kotlin创建一个只读属性,如以下示例所示:

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

要访问该字段值,请使用set()和get()访问器方法,如下所示:

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

Observable collections

一些应用程序使用动态结构来保存数据。 可观察的集合Observable collections允许使用key(键)访问这些结构。

ObservableArrayMap

当key(键)是引用类型(例如String)时,ObservableArrayMap类非常有用,如以下示例所示:

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

在布局中,可以使用字符串键找到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类非常有用,如下所示:

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

在布局中,可以通过索引访问列表,如以下示例所示:

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

Observable objects

实现Observable接口的类允许注册要在可观察对象上进行属性更改通知的侦听器。

Observable接口具有添加和删除侦听器的机制,但是您必须确定何时发送通知。 为了简化开发,数据绑定库提供了BaseObservable类,该类实现了侦听器注册机制。

实现BaseObservable的数据类负责在属性更改时发出通知。这是通过为getter分配Bindable注解并在setter中调用notifyPropertyChanged()方法来完成的,如以下示例所示:

public 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(com.eallcn.mlw.rentcustomer.BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(com.eallcn.mlw.rentcustomer.BR.lastName);
    }
}
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.firstName}" />
  • l 数据绑定在模块包中生成一个名为BR的类,其中包含用于数据绑定的资源的ID。

Bindable注解在编译期间会在BR类文件中生成一个条目。可以在xml中使用。

  • l 如果不能更改数据类的基类,那么我们可以impletement Observable接口,并自己通过使用PropertyChangeRegistry对象来以高效地注册和通知侦听器。

Bindable注解

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
public @interface Bindable {
    String[] value() default  {};
}

Bindable 注解应该应用于一个Observable 类的任何getter访问方法。Bindable注解在编译期间会在BR类文件中生成一个条目。

Bindable注解可以设置一个依赖属性列表。如果列出的属性中有一个更改通知,那么Bindable注解的这个值也会被认为是dirty的,并会被刷新。例如:

@Bindable
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
    notifyPropertyChanged(com.example.jetpackdemo.BR.name);
}

@Bindable
public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
    notifyPropertyChanged(com.example.jetpackdemo.BR.age);
}

@Bindable({"name", "age"})
public String getNameAndAge() {
    return name + "-" + age;
}

在BR中会生成一个名为nameAndAge的item,那么在xml中就也可以使用。

每当age或name有更改通知时,nameAndAge也会被认为是dirty的。但这并不意味着Observable.OnPropertyChangedCallback.onPropertyChanged(Observable, int)将得到BR.nameAndAge的通知,只有包含nameAndAge的绑定表达式将被刷新。

和LiveData一起使用

LiveData是生命周期感知的对象,所以如果在xml的绑定表达式中引入了LiveData,那么就必须在生成的绑定对象里设置LifecycleOwner,否则LiveData不能被观察,对LiveData的数据更新也不会传播到UI。

@MainThread
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) 

此方法是ViewDataBinding类的成员方法。

自定义生成的绑定类

生成的绑定类将布局变量与布局中的视图链接。 绑定类的名称和包可以自定义。 所有生成的绑定类都从ViewDataBinding类继承。

xml中带有id的view

对于有id的view,会在生成的绑定类里生成一个id名字的view的引用,我们可以直接通过此引用直接操作此view。

例如:

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

那么在生成的绑定类里,就会生成两个TextView的变量,一个叫firstName,一个叫lastName,他们都是public final修饰的。

立即绑定executePendingBindings

当变量或可观察对象发生更改时,绑定将安排在下一帧之前更改。

但是,有时绑定必须立即执行。 要强制执行,请使用executePendingBindings()方法。

在RecyclerView的adapter中的onBindViewHolder使用时

在RecyclerView的adapter的onBindViewHolder中一般需要使用executePendingBindings()

由于不使用 executePendingBindings()时,数据设置到ui上需要等到下一帧,而此时当要绑定一个数据的时候,RecyclerView 会调用 onBindViewHolder,让你去准备测量这个布局,而我们的ui还没设置数据,所以此时ui可能就会闪烁一下。

不使用executePendingBindings()有可能还会导致图片闪烁,

原因可能是 由于不使用 executePendingBindings()时,数据设置到ui上需要等到下一帧,而如果在此期间设置数据多次,那么最后ui显示时会突然显示几次。

Dynamic Variables

生成的绑定类是继承自ViewDataBinding,

有时并不知道绑定对象是哪个具体的绑定类的对象,那么此时可以使用ViewDataBinding的一个方法setVariable,这个方法是每个生成的绑定类都会实现的,那么调用此方法就相当于调用具体的绑定类的对象的某个set方法。

public boolean setVariable(int variableId, @Nullable Object variable)

variableId:可以使用生成的BR类中的某个id。

Background Thread

您可以在后台线程中更改数据模型,但前提是这个模型不是集合。数据绑定会在求值过程中对每个变量/字段进行本地化,以避免出现并发问题。

自定义生成的绑定类名

默认情况下,将基于布局文件的名称生成绑定类,该绑定类以大写字母开头,去掉下划线(_)大写下划线之后的字母并在后边加上Binding后缀。

该类位于模块包下的databinding包中。 例如,布局文件contact_item.xml生成ContactItemBinding类。 如果模块包是com.example.my.app,则将绑定类放置在com.example.my.app.databinding包中。

可以通过绑定布局中的data的class属性来修改类名或包名。

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

这种方式可以修改类名,包还是com.example.my.app.databinding

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

这种方式可以修改类名,包变成com.example.my.app

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

这种方式可以修改包名和类名,变成com.example包下的ContactItem类

Binding adapters

Binding adapters负责进行适当的框架调用以设置值。

  • l 一个示例是设置属性值,例如调用setText()方法。
  • l 另一个示例是设置事件侦听器,例如调用setOnClickListener()方法。

数据绑定库 允许您指定调用的方法来设置值,提供自己的绑定逻辑 以及 使用适配器指定返回对象的类型。

指定属性setter方法

每当绑定值更改时,生成的绑定类都会 使用绑定表达式 调用View对应的setter方法。 您可以允许数据绑定库自动确定方法,也可以显式声明该方法,或提供自定义逻辑来选择方法。

!ViewStub不支持、include不支持!

自动选择setter方法

1. 对于名为example的属性,databinding库会自动尝试查找setExample(arg)方法,查找时会考虑 在布局中写的绑定表达式返回的类型 是否和setExample接收的参数类型是否匹配。

在搜索方法时不考虑属性的名称空间,在搜索方法时仅使用属性名称和类型。

例如:现有一个绑定表达式 android:text="@{user.name}",那么绑定库就会去查找此view的setText方法,

如果user.getName()返回的是String,那么会去找接收的参数和String兼容的setText方法;

而如果user.getName()返回的是int,那么会去找接收的参数和int兼容的setText方法;

当然我们可以通过强转来指定调用的方法。

2. 即使没有给定名称的view属性,数据绑定也可以工作,前提是此view得有此名称的setter方法。

比如support库中的Drawerlayout,它是没有什么自定义的属性的,但有很多setter方法,那么如下使用:

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

那么会自动的查找到对应的setScrimColor(int) 和 setDrawerListener(DrawerListener)。

指定自定义的方法名BindingMethods

一些view的属性的setter方法可能和属性名并无关联,这种情况下就可以使用@BindingMethods 注解来处理。

@Target({ElementType.TYPE})
public @interface BindingMethods {
    BindingMethod[] value();
}
@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {

    /**
     * 属性关联的view的class
     */
    Class type();

    /**
     * 如果属性是android自带的属性,那么此字符串就是android:xxx;
如果是自定义的属性,那么字符串就是属性名即可,xxx,但在xml中app:xxx
!可以没有定义过!
     */
    String attribute();

    /**
     * @return The method to call to set the attribute value.
     */
    String method();
}

BindingMethods是可以添加到应用程序中任何类的注解

例如:

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

大部分 官方的view的属性对应的setter不匹配的属性,databinding库已经添加了很多BindingMethods注解,比如TextViewBindingAdapter对textview的一些不匹配的属性添加了BindingMethods注解。

指定自定义方法BindingAdapter

和上边的指定方法名不一样,此方式是静态方法,而上边的是成员方法。

比如有些属性是没有setter方法的,就是说没有 方法名匹配 和 参数兼容的方法,比如android:paddingLeft,但是view是有setPadding(left, top, right, bottom)方法的,那么此时就可以在一个静态方法上使用BindingAdapter注解。

@Target(ElementType.METHOD)
public @interface BindingAdapter {

    /**
     * @return The attributes associated with this binding adapter.
接收的参数列表,如果是Android自带的需要android:xxx;
如果是自定义的属性,则直接写属性,但在xml中app:xxx
!可以没有定义过!
     */
    String[] value();

    /**
     * 是否需要每个属性都需要有绑定表达式。
当false时,属性列表中至少有一个是有绑定表达式的,那么此注解的方法将被调用,那些没有绑定表达式的属性会传递java的默认值。
必须注意确保默认值不要与有效的XML值混淆。
     *
     * @return whether or not every attribute must be assigned a binding expression. The default value is true.
     */
    boolean requireAll() default true;
}
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
    view.setPadding(padding,
            view.getPaddingTop(),
            view.getPaddingRight(),
            view.getPaddingBottom());
}

第一个参数View 是与属性关联的视图的类型。

第二个参数确定给定属性在绑定表达式中接受的类型。

databinding库已经提供了很多默认的BindingAdapter,比如ViewBindingAdapter中的,TextViewBindingAdapter中的,

当发生冲突时,您定义的BindingAdapter将覆盖databinding库提供的默认适配器。

requireAll

<ImageView 
app:imageUrl="@{venue.imageUrl}" 
app:error="@{@drawable/venueError}" />
@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);
  }
}

oldValue

BindingAdapter可以选择在其处理程序中采用旧值。 采用旧值和新值的方法:应首先声明属性的所有旧值,然后再声明新值,如以下示例所示:

@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());
   }
}

Event handlers一起使用

事件处理程序 只能与具有一种抽象方法的接口或抽象类一起使用,如以下示例所示:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
@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.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);
}
@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);
        }
    }
}

对象类型转换

这里所说的转换是指使用绑定表达式时,存在的类型不一致的处理。而普通布局是不存在这种问题的。

自动转换

从绑定表达式返回Object时,库将选择用于设置属性值的方法。 将对象强制转换为所选方法的参数类型。

在使用ObservableMap类存储数据的应用程序中,此行为很方便,如以下示例所示:

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

自定义转换BindingConversion

在某些情况下,需要在特定类型之间进行自定义转换。

例如,视图的android:background属性需要Drawable,但是指定的颜色值为整数。 以下示例显示了一个期望使用Drawable的属性,但是提供了一个整数:

<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);
}
@Target({ElementType.METHOD})
public @interface BindingConversion {
}

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

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

猜你喜欢

转载自www.cnblogs.com/muouren/p/12368354.html