Databanding

數(shù)據(jù)綁定布局

數(shù)據(jù)綁定布局文件以根標(biāo)記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>
    

數(shù)據(jù)對(duì)象

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

可觀察性是指一個(gè)對(duì)象將其數(shù)據(jù)變化通知給其他對(duì)象的能力。通過數(shù)據(jù)綁定庫,您可以讓對(duì)象、字段或集合變?yōu)榭捎^察。

當(dāng)其中一個(gè)可觀察數(shù)據(jù)對(duì)象綁定到界面并且該數(shù)據(jù)對(duì)象的屬性發(fā)生更改時(shí),界面會(huì)自動(dòng)更新。

  • 可觀察字段
    在創(chuàng)建實(shí)現(xiàn) Observable接口的類時(shí)要完成一些操作,但如果您的類只有少數(shù)幾個(gè)屬性,則這樣操作的意義不大。在這種情況下,您可以使用通用Observable類和以下特定于基元的類,將字段設(shè)為可觀察字段:

  • ObservableBoolean

  • ObservableByte

  • ObservableChar

  • ObservableShort

  • ObservableInt

  • ObservableLong

  • ObservableFloat

  • ObservableDouble

  • ObservableParcelable

可觀察字段是具有單個(gè)字段的自包含可觀察對(duì)象。原語版本避免在訪問操作期間封箱和開箱。要使用此機(jī)制,請(qǐng)采用 Java 編程語言創(chuàng)建 public final 屬性,或在 Kotlin 中創(chuàng)建只讀屬性,如以下示例所示:

    private static class User {
        public final ObservableField<String> firstName = new ObservableField<>();
        public final ObservableField<String> lastName = new ObservableField<>();
        public final ObservableInt age = new ObservableInt();
    }
  • 可觀察觀察集合
    某些應(yīng)用使用動(dòng)態(tài)結(jié)構(gòu)來保存數(shù)據(jù)。可觀察集合允許使用鍵訪問這些結(jié)構(gòu)。當(dāng)鍵為引用類型(如 String)時(shí),ObservableArrayMap 類非常有用,如以下示例所示:
    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"/>

當(dāng)鍵為整數(shù)時(shí),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"/>
  • 可觀察對(duì)象
    實(shí)現(xiàn)Observable接口的類允許注冊(cè)監(jiān)聽器,以便它們接收有關(guān)可觀察對(duì)象的屬性更改的通知。

Observable接口具有添加和移除監(jiān)聽器的機(jī)制,但何時(shí)發(fā)送通知?jiǎng)t必須由您決定。為便于開發(fā),數(shù)據(jù)綁定庫提供了用于實(shí)現(xiàn)監(jiān)聽器注冊(cè)機(jī)制的BaseObservable類。實(shí)現(xiàn)BaseObservable的數(shù)據(jù)類負(fù)責(zé)在屬性更改時(shí)發(fā)出通知。具體操作過程是向 getter 分配Bindable注釋,然后在 setter 中調(diào)用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);
        }
    }

綁定數(shù)據(jù)

系統(tǒng)會(huì)為每個(gè)布局文件生成一個(gè)綁定類。默認(rèn)情況下,類名稱基于布局文件的名稱,此類包含從布局屬性(例如,user 變量)到布局視圖的所有綁定,并且知道如何為綁定表達(dá)式指定值。建議的綁定創(chuàng)建方法是在擴(kuò)充布局時(shí)創(chuàng)建,如以下示例所示:

    @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,ListViewRecyclerView適配器中使用數(shù)據(jù)綁定項(xiàng),您可能更愿意使用綁定類或DataBindingUtil類的inflate()方法,如以下代碼示例所示:

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

方法引用、監(jiān)聽綁定

方法引用監(jiān)聽器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的。如果您希望在事件發(fā)生時(shí)對(duì)表達(dá)式求值,則應(yīng)使用監(jiān)聽器綁定。

  • 方法引用
    在方法引用中,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)匹配。
    public class MyHandlers {
        public void onClickFriend(View view) { ... }
    }

綁定表達(dá)式可將視圖的點(diǎn)擊監(jiān)聽器分配給 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>
  • 監(jiān)聽綁定
    在監(jiān)聽器綁定中,只有您的返回值必須與監(jiān)聽器的預(yù)期返回值相匹配(預(yù)期返回值無效除外)。例如,請(qǐng)參考以下具有 onSaveClick() 方法的 presenter 類:
    public class Presenter {
        public void onSaveClick(Task task){}
    }

然后,您可以將點(diǎn)擊事件綁定到 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>
    

綁定適配器

數(shù)據(jù)綁定庫允許您通過使用適配器指定為設(shè)置值而調(diào)用的方法、提供您自己的綁定邏輯,以及指定返回對(duì)象的類型。

  • 自動(dòng)選擇方法

對(duì)于名為 example 的特性,庫自動(dòng)嘗試查找接受兼容類型作為參數(shù)的方法 setExample(arg)。系統(tǒng)不會(huì)考慮特性的命名空間,搜索方法時(shí)僅使用特性名稱和類型。

android:text="@{user.name}" 表達(dá)式為例,庫會(huì)查找接受 user.getName() 所返回類型的 setText(arg) 方法。如果 user.getName() 的返回類型為 String,則庫會(huì)查找接受 String 參數(shù)的 setText() 方法。如果表達(dá)式返回的是 int,則庫會(huì)搜索接受 int 參數(shù)的 setText() 方法。表達(dá)式必須返回正確的類型,您可以根據(jù)需要強(qiáng)制轉(zhuǎn)換返回值的類型。

即使不存在具有給定名稱的特性,數(shù)據(jù)綁定也會(huì)起作用。然后,您可以使用數(shù)據(jù)綁定為任何 setter 創(chuàng)建特性。例如,支持類DrawerLayout 沒有任何特性,但有很多 setter。以下布局會(huì)自動(dòng)將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。在這些情況下,某個(gè)特性可能會(huì)使用BindingMethods 注釋與 setter 相關(guān)聯(lián)。注釋與類一起使用,可以包含多個(gè)BindingMethod注釋,每個(gè)注釋對(duì)應(yīng)一個(gè)重命名的方法。綁定方法是可添加到應(yīng)用中任何類的注釋。在以下示例中,android:tint 特性與setImageTintList(ColorStateList) 方法相關(guān)聯(lián),而不與 setTint() 方法相關(guān)聯(lián):

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

大多數(shù)情況下,您無需在 Android 框架類中重命名 setter。特性已使用命名慣例實(shí)現(xiàn),可自動(dòng)查找匹配的方法。

  • 提供自定義邏輯

某些特性需要自定義綁定邏輯。例如,android:paddingLeft 特性沒有關(guān)聯(lián)的 setter,而是提供了 setPadding(left, top, right, bottom) 方法。使用BindingAdapter注釋的靜態(tài)綁定適配器方法支持自定義特性 setter 的調(diào)用方式。

Android 框架類的特性已經(jīng)創(chuàng)建了 BindingAdapter 注釋。例如,以下示例展示了 paddingLeft 特性的綁定適配器:

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

參數(shù)類型非常重要。第一個(gè)參數(shù)用于確定與特性關(guān)聯(lián)的視圖類型,第二個(gè)參數(shù)用于確定在給定特性的綁定表達(dá)式中接受的類型。

您還可以使用接收多個(gè)特性的適配器。如果 ImageView 對(duì)象同時(shí)使用了 imageUrlerror,并且 imageUrl 是字符串,errorDrawable,則會(huì)調(diào)用適配器。
如以下示例所示:

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

如果您希望在設(shè)置了任意特性時(shí)調(diào)用適配器,則可以將適配器的可選 requireAll 標(biāo)記設(shè)置為 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);
      }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容