一、前言

上一篇文章简单的对`DataBinding`有一个初步的了解。这里会对剩下的内容进行一个记录。这里主要参考类官方文档

二、对象引用

`DataBinding`允许在布局里面直接使用一些对象。这些对象可以通过在Java/Kotlin代码中对其修改使其改变UI内容。比如`String`、`Integer`之类的。这些类可以进行直接定义。如下:

    <data>
        <variable
            name="change"
            type="String" />
        <variable
            name="intValue"
            type="Integer" />
    </data>

如果是一些自定义对象则需要填写完整路径,如下:

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

上述定义方式是我们可以很容易的使用对象。如下:

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

但是如果我们需要使用某些类的常量的话就需要使用<import>的方式了。例如使用View.VISIBLE

<data>
        <import type="android.view.View"/>
 </data>
    
<TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="@{View.VISIBLE}"/>
    

假如要使用两个类名一样的对象的话,可以使用alias进行区分。

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

有时候会导入一些带范型的类,会用到尖括号<>,在xml布局里面需要使用&lt;来进行替换。

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

在实际使用时可能会涉及到类型强转,使用方式可Java中的方式一样

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

如果布局中使用了<include>标签,那么对象需要进行传递,使用如下方式

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

name.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="value"
            type="String" />
        <variable
            name="user"
            type="com.example.myapplication.User" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/update_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@{user.name}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            tools:text="value" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

数据绑定不支持 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.example.User"/>
       </data>
       <merge><!-- Doesn't work -->
           <include layout="@layout/name"
               bind:user="@{user}"/>
           <include layout="@layout/contact"
               bind:user="@{user}"/>
       </merge>
    </layout>
    

需要注意的是bind:user="user"中的第一个user<include>布局中定义的user。第二个user是宿主布局的user

三、绑定表达式

1、基础用法

数据绑定支持在布局里面可以使用一些常见的表达式,其使用方式与Java代码基本上一样,其支持的表达式如下

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 &lt;
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

示例代码:

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

DataBinding还支持其它的表达式。

Null合并运算符

android:text="@{user.displayName ?? user.lastName}"
<!--等效于以下代码-->
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

视图引用

表达式可以通过以下语法按 ID 引用布局中的其他视图

android:text="@{exampleText.text}"

注意:绑定类将 ID 转换为驼峰式大小写。

在以下示例中,TextView 视图引用同一布局中的 EditText 视图

<EditText
        android:id="@+id/example_text"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
    <TextView
        android:id="@+id/example_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{exampleText.text}"/>

使用该功能可以实现如输入框的内容更改后将内容显示到文本控件上面

DataBinding支持在布局里面使用一些values文件夹下面的资源。

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

您可以通过提供参数来评估格式字符串和复数形式:

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

您可以将属性引用视图引用作为资源参数进行传递(这里想不到视图引用的场景):

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

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

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

2、函数引用

DataBinding中除了赋值问题,还可以对一些事件进行绑定,例如onClick事件。

这里有两种方式进行绑定

方式一( 方法引用):

    class MyHandlers {
        fun 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>

方式二(监听器绑定):

    class Presenter {
        fun 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>
    

两者的区别是:

  1. 方法引用是数据绑定时就创建好了,监听器绑定是事件触发时候才绑定好

  2. 方法引用需要与事件监听器的参数匹配。监听器绑定则只需要返回值与事件监听器一致即可。

在监听器绑定中可以对参数进行忽略,也可以命名参数,取决于实际需求。上述的代码是忽略了参数。如果想要命名参数可以使用以下方式:

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

四、参考链接

  1. 布局和绑定表达式  |  Android 开发者  |  Android Developers