美文网首页Android Jetpack
Android Jetpack架构篇:Data Binding(

Android Jetpack架构篇:Data Binding(

作者: walker不抽烟 | 来源:发表于2018-11-06 11:06 被阅读78次

    Android Jetpack架构篇:Data Binding(一)

    <spane id="layouts_binding_exp">Layouts and binding expressions(布局和绑定表达式)</span>

    该表达式语言,允许你通过编写表达式来处理视图的事件分发。数据绑定库(Data Binding Library)自动生成将布局中的视图与data对象绑定所需的类。

    数据绑定布局文件略有不同,必须以 layout 标记开头,后跟 data 元素和 view 根元素。 view 元素必须是一个非绑定的布局文件。下面是布局文件样例:

    <?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 元素中的变量user描述了可在此布局中使用的属性。

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

    布局中的使用 "@{}" 语法来使用属性。这里,TextView文本设置为user变量的 firstName属性:

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

    注意:布局表达式应保持短小且简单,因为它们不能进行单元测试并且IDE支持有限。为了简化布局表达式,您可以使用自定义绑定适配器。

    Data object(数据对象)

    假设有一个普通的实体对象User:

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

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

    Binding data(绑定数据)

    为每个布局文件生成绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。上面的布局文件名是 activity_main.xml相应生成的类 MainActivityBinding。此类包含布局属性(例如,user变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。建议绑定的推荐方法是在扩展布局时执行此操作,如图所示在以下示例中:

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

    注意:上面MainActivityBinding是官网中写法,正确写法是ActivityMainBinding请读者自行替换,下同。

    在运行时,应用程序在UI中显示Test用户。或者,您可以使用a获取视图LayoutInflater,如以下示例所示:

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

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

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

    表达语言

    一般特征

    表达式语言看起来很像托管代码中的表达式。您可以在表达式语言中使用以下运算符和关键字:

    • 基本运算 + - / * %
    • 字符串连接 +
    • 逻辑表达式 && ||
    • 二进制 & | ^
    • 一元运算符 + - ! ~
    • 移位 >> >>> <<
    • 比较运算 == > < >= <=
    • instanceof
    • 分组 ()
    • 字面值 - 字符,字符串,数字, null
    • 强转
    • 方法调用
    • 属性访问
    • 数组访问 []
    • 三元运算符 ?:

    举例:

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

    缺少操作

    您可以在托管代码中使用的表达式语法中缺少以下操作:

    • this
    • super
    • new
    • 显式通用调用

    空结合运算符

    空合并运算符(??)如果不是,则选择左操作数,如果是null 前者,则选择右操作数null。

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

    这在功能上等价于:

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

    属性引用

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

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

    空指针异常避免

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

    集合

    可以使用[]运算符访问普通集合,例如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&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中的值。例如,@{map[key]}在上面的示例中可以替换为 @{map.key}

    字符串文字

    您可以使用单引号括起属性值,这允许在表达式中使用双引号,如以下示例所示:

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

    也可以使用双引号来包围属性值。这时,字符串文字应该用后引号括起来`:

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

    资源

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

    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

    事件处理

    数据绑定允许您编写从视图调度的表达式处理事件(例如,onClick()方法)。事件属性名称由监听器方法的名称确定,但有一些例外。例如,View.OnClickListener有一个方法onClick(),所以这个事件的属性是android:onClick。

    对于click事件,有一些专门的事件处理程序需要除android:onClick避免冲突之外的属性。您可以使用以下属性来避免这些类型的冲突:

    Class Listener setter Attribute
    SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
    ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
    ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

    您可以使用以下机制来处理事件:

    • 方法引用:在表达式中,您可以引用符合监听器方法签名的方法。当表达式求值为方法引用时,Data绑定将方法引用和所有者对象包装在监听器中,并在目标view上设置该监听器。如果表达式求值为null,则数据绑定不会创建监听器并设置null监听器。
    • 监听器绑定:这些是在事件发生时计算的lambda表达式。数据绑定总是创建一个监听器,它在view上设置。调度事件时,监听器会计算lambda表达式。

    方法引用

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

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

    要将事件分配给其处理程序,请使用普通绑定表达式,其值为要调用的方法名称。例如,请考虑以下示例数据对象:

    public class MyHandlers {
        public void onClickFriend(View view) { ... }
    }
    

    绑定表达式可以将View的单击监听器分配给 onClickFriend()方法,如下所示:

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

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

    监听器绑定

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

    在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值匹配(除非它期望void)。例如,请考虑以下具有该onSaveClick() 方法的Presenter 类:

    public class Presenter {
        public void onSaveClick(Task task){}
    }
    

    然后,您可以将click事件绑定到onSaveClick()方法,如下所示:

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

    在表达式中使用回调时,数据绑定会自动创建必要的监听器并注册事件。当View触发事件时,数据绑定会计算给定的表达式。与常规绑定表达式一样,在计算这些监听器表达式时,仍然可以获得数据绑定的null和线程安全性。

    在上面的示例中,我们尚未定义view传递给的参数onClick(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表达式:

    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,则你的表达式也必须返回相同类型的值。例如,如果要监听长按事件,则应返回表达式boolean。

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

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

    如果需要使用带断言的表达式(例如:三元运算),则可以使用void。

    android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
    
    避免复杂的监听器

    监听器表达式非常强大,可以使您的代码非常容易阅读。另一方面,包含复杂表达式的监听器使您的布局难以阅读和维护。这些表达式应该像将UI中的可用数据传递给回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。

    Imports、variables和includes

    数据绑定库提供导入,变量和包含等功能。导入使布局文件中的类很容易引用。变量允许您描述可用于绑定表达式的属性。包括让您在整个应用中重复使用复杂的布局。

    Imports

    导入允许您轻松引用布局文件中的类,就像在托管代码中一样。import可以在data 元素内使用零个或多个元素。以下代码示例将View类导入布局文件:

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

    导入的View类,可以在绑定表达式中引用它。以下示例显示如何引用View类的常量VISIBLEGONE

    <TextView
       android:text="@{user.lastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
    
    Type aliases(类型别名)

    当存在类名冲突时,可以将其中一个类重命名为别名。以下示例将包中的View类 重命名com.example.real.estateVista

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

    这样,可以用Vista来引用com.example.real.estate.View,而View引用android.view.View

    导入其他类

    导入的类型可以用作变量和表达式中的类型引用。以下示例显示UserList用作变量的类型:

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

    警告:Android Studio尚未处理导入,因此导入变量的自动完成功能可能无法在IDE中运行。您的应用程序仍在编译,您可以通过在变量定义中使用完全限定名称来解决IDE问题。

    您还可以使用导入的类型来进行强转。以下示例将connection属性强制类型转换为User

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

    在表达式中引用静态字段和方法时,也可以使用导入的类型。以下代码导入MyStringUtils类并引用其capitalize方法:

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

    就像在托管代码中一样,java.lang.*会自动导入。

    variables(变量)

    您可以variable在元素内使用多个元素data。每个 variable元素都描述了一个属性,该属性可以在布局中设置,以便在布局文件中的绑定表达式中使用。下面的示例声明的user,image和note变量:

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

    在编译时检查变量类型,因此如果变量实现Observableobservable collection,则应该在类型中反射。如果变量的基类或接口未实现Observable接口,则不会观察变量。

    当存在用于各种配置的不同布局文件(例如,横向或纵向)时,组合变量。这些布局文件之间不得存在冲突的变量定义。

    生成的绑定类对于每个描述的变量都有一个setter和getter。调用setter之前,变量采用托管代码默认值 -- 引用类型为null,int为0,boolean为false等。

    特殊变量context,可由绑定表达式生成。context的值是由根View的getContext()方法获取的Context对象。context变量由具有该名称的显式变量声明覆盖。

    includes(包含)

    通过使用app命名空间和属性中的变量名,变量可以从包含的布局传递到包含的布局绑定中。以下示例显示user了name.xml和 contact.xml布局文件中包含的变量:

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

    数据绑定不支持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>
    




    参考
    [1] https://developer.android.com/topic/libraries/data-binding/
    [2] https://developer.android.com/topic/libraries/data-binding/expressions

    相关文章

      网友评论

        本文标题:Android Jetpack架构篇:Data Binding(

        本文链接:https://www.haomeiwen.com/subject/mlgyxqtx.html