Databanding

2020-05-13  本文已影响0人  爱做梦的严重精神病患者

数据绑定布局

数据绑定布局文件以根标记layout开头,后跟data元素和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>
    

数据对象

    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;
      }
    }
可观察性

可观察性是指一个对象将其数据变化通知给其他对象的能力。通过数据绑定库,您可以让对象、字段或集合变为可观察。

当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。

可观察字段是具有单个字段的自包含可观察对象。原语版本避免在访问操作期间封箱和开箱。要使用此机制,请采用 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();
    }
    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);

在布局中,可使用字符串键找到地图,如下所示:

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

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

<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接口具有添加和移除监听器的机制,但何时发送通知则必须由您决定。为便于开发,数据绑定库提供了用于实现监听器注册机制的BaseObservable类。实现BaseObservable的数据类负责在属性更改时发出通知。具体操作过程是向 getter 分配Bindable注释,然后在 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);
        }
    }

绑定数据

系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

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

如果您要在FragmentListViewRecyclerView适配器中使用数据绑定项,您可能更愿意使用绑定类或DataBindingUtil类的inflate()方法,如以下代码示例所示:

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

方法引用、监听绑定

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

    public class MyHandlers {
        public void onClickFriend(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>
    public class Presenter {
        public void onSaveClick(Task task){}
    }

然后,您可以将点击事件绑定到 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>
    

绑定适配器

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

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

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

即使不存在具有给定名称的特性,数据绑定也会起作用。然后,您可以使用数据绑定为任何 setter 创建特性。例如,支持类DrawerLayout 没有任何特性,但有很多 setter。以下布局会自动将setScrimColor(int)setDrawerListener(DrawerListener)) 方法分别用作 app:scrimColorapp:drawerListener 特性的 setter:

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

某些特性拥有名称不符的 setter。在这些情况下,某个特性可能会使用BindingMethods 注释与 setter 相关联。注释与类一起使用,可以包含多个BindingMethod注释,每个注释对应一个重命名的方法。绑定方法是可添加到应用中任何类的注释。在以下示例中,android:tint 特性与setImageTintList(ColorStateList) 方法相关联,而不与 setTint() 方法相关联:

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

大多数情况下,您无需在 Android 框架类中重命名 setter。特性已使用命名惯例实现,可自动查找匹配的方法。

某些特性需要自定义绑定逻辑。例如,android:paddingLeft 特性没有关联的 setter,而是提供了 setPadding(left, top, right, bottom) 方法。使用BindingAdapter注释的静态绑定适配器方法支持自定义特性 setter 的调用方式。

Android 框架类的特性已经创建了 BindingAdapter 注释。例如,以下示例展示了 paddingLeft 特性的绑定适配器:

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

参数类型非常重要。第一个参数用于确定与特性关联的视图类型,第二个参数用于确定在给定特性的绑定表达式中接受的类型。

您还可以使用接收多个特性的适配器。如果 ImageView 对象同时使用了 imageUrlerror,并且 imageUrl 是字符串,errorDrawable,则会调用适配器。
如以下示例所示:

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

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

如果您希望在设置了任意特性时调用适配器,则可以将适配器的可选 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);
      }
    }
上一篇 下一篇

猜你喜欢

热点阅读