Jetpack ---- Data Binding入門(二)

detachment_w發表於2020-12-16

翻譯自android官網,可直接去官網觀看

使用可觀察的資料物件

可觀察性是指一個物件將其資料變化告知其他物件的能力。通過資料繫結庫,您可以讓物件、欄位或集合變為可觀察

任何 plain-old 物件都可用於資料繫結,但修改物件不會自動使介面更新通過資料繫結,資料物件可在其資料發生更改時通知其他物件,即監聽器。可觀察類有三種不同型別:物件、欄位和集合

當其中一個可觀察資料物件繫結到介面並且該資料物件的屬性發生更改時,介面會自動更新

可觀察欄位

在建立實現 Observable 介面的類時要完成一些操作,但如果您的類只有少數幾個屬性,這樣操作的意義不大在這種情況下,您可以使用通用 Observable 類和以下primitive-specific的類,將欄位設為可觀察欄位

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • 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() 訪問器方法,或使用 Kotlin 屬性語法

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

注意:Android Studio 3.1 及更高版本允許用 LiveData 物件替換可觀察欄位,從而為您的應用提供額外的好處。如需瞭解詳情,請參閱使用 LiveData 將資料變化通知給介面

可觀察集合

某些應用使用動態結構來儲存資料。可觀察集合允許使用鍵訪問這些結構。當鍵為引用型別(如 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<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 介面的類允許註冊監聽器,以便它們接收有關可觀察物件的屬性更改的通知

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

資料繫結在模組包中生成一個名為 BR 的類,該類包含用於資料繫結的資源的 ID。在編譯期間,Bindable 註釋會在 BR 類檔案中生成一個條目如果資料類的基類無法更改,Observable 介面可以使用 PropertyChangeRegistry 物件實現,以便有效地註冊和通知監聽器

生成的繫結類

資料繫結庫可以生成用於訪問佈局的變數和檢視的繫結類。本頁介紹瞭如何建立和自定義生成的繫結類。

生成的繫結類將佈局變數與佈局中的檢視關聯起來。繫結類的名稱和包可以自定義。所有生成的繫結類都是從 ViewDataBinding 類繼承而來的

系統會為每個佈局檔案生成一個繫結類。預設情況下,類名稱基於佈局檔案的名稱,它會轉換為 Pascal 大小寫形式並在末尾新增 Binding 字尾。以上佈局檔名為 activity_main.xml,因此生成的對應類為 ActivityMainBinding。此類包含從佈局屬性(例如,user 變數)到佈局檢視的所有繫結,並且知道如何為繫結表示式指定值

建立繫結物件

在對佈局進行inflating後立即建立繫結物件,以確保檢視層次結構在通過表示式與佈局內的檢視繫結之前不會被修改將物件繫結到佈局的最常用方法是在繫結類上使用靜態方法。您可以使用繫結類的 inflate() 方法來擴充檢視層次結構並將物件繫結到該層次結構,如以下示例所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());

    setContentView(binding.root);
}

inflate() 方法還有另外一個版本,這個版本不僅使用 LayoutInflater 物件,還使用 ViewGroup 物件,如以下示例所示:

MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);

如果佈局是使用其他機制inflated的,可單獨繫結,如下所示:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時,系統無法預先知道繫結型別。在這種情況下,可以使用 DataBindingUtil 類建立繫結,如以下程式碼段所示:

View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);

如果您要在 Fragment、ListView 或 RecyclerView 介面卡中使用資料繫結項,您可能更願意使用繫結類或 DataBindingUtil 類的 inflate() 方法,如以下程式碼示例所示:

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

帶 ID 的檢視

資料繫結庫會針對佈局中具有 ID 的每個檢視在繫結類中建立不可變欄位。例如,資料繫結庫會根據以下佈局建立 TextView 型別的 firstName 和 lastName 欄位:

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

該庫一次性從檢視層次結構中提取包含 ID 的檢視。相較於針對佈局中的每個檢視呼叫 findViewById() 方法,這種機制速度更快

如果沒有資料繫結,則 ID 並不是必不可少的,但仍有一些情況必須能夠從程式碼訪問檢視

變數

資料繫結庫為佈局中宣告的每個變數生成訪問器方法。例如,以下佈局在繫結類中針對 user、image 和 note 變數生成了 setter 和 getter 方法:

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

ViewStubs

與普通檢視不同,ViewStub 物件初始是一個不可見檢視。當它們顯示出來或者獲得明確指示進行擴充時,它們會通過擴充另一個佈局在佈局中完成自我取代

由於 ViewStub 實際上會從檢視層次結構中消失,因此繫結物件中的檢視也必須消失,才能通過垃圾回收進行回收。由於檢視是最終結果,因此 ViewStubProxy 物件將取代生成的繫結類中的 ViewStub,讓您能夠訪問 ViewStub(如果存在),同時還能訪問 ViewStub 進行擴充後的擴充版檢視層次結構

在擴充其他佈局時,必須為新佈局建立繫結。因此,ViewStubProxy 必須監聽 ViewStub OnInflateListener 並在必要時建立繫結。由於在給定時間只能有一個監聽器,因此 ViewStubProxy 允許您設定 OnInflateListener,它將在建立繫結後呼叫這個監聽器

即時繫結

當可變或可觀察物件發生更改時,繫結會按照計劃在下一幀之前發生更改。但有時必須立即執行繫結。要強制執行,請使用 executePendingBindings() 方法

高階繫結

動態變數

有時,系統並不知道特定的繫結類。例如,針對任意佈局執行的 RecyclerView.Adapter 不知道特定繫結類。在呼叫 onBindViewHolder() 方法時,仍必須指定繫結值。

在以下示例中,RecyclerView 繫結到的所有佈局都有 item 變數。BindingHolder 物件具有一個 getBinding() 方法,這個方法返回 ViewDataBinding 基類

public void onBindViewHolder(BindingHolder holder, int position) {
    final T item = items.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
}

注意:資料繫結庫在模組包中生成一個名為 BR 的類,其中包含用於資料繫結的資源的 ID。在上例中,該庫自動生成 BR.item 變數。

後臺執行緒

可以在後臺執行緒中更改資料模型,但前提是這個模型不是集合資料繫結會在求值過程中對每個變數/欄位進行本地化,以避免出現併發問題

自定義繫結類名稱

預設情況下,繫結類是根據佈局檔案的名稱生成的,以大寫字母開頭,移除下劃線 ( _ ),將後一個字母大寫,最後新增字尾 Binding該類位於模組包下的 databinding 包中。例如,佈局檔案 contact_item.xml 會生成 ContactItemBinding 類。如果模組包是 com.example.my.app,則繫結類放在 com.example.my.app.databinding 包中。

通過調整 data 元素的 class 特性,繫結類可重新命名或放置在不同的包中。例如,以下佈局在當前模組的 databinding 包中生成 ContactItem 繫結類:

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

您可以在類名前新增句點和字首,從而在其他檔案包中生成繫結類。以下示例在模組包中生成繫結類:

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

還可以使用完整軟體包名稱來生成繫結類。以下示例在 com.example 包中建立 ContactItem 繫結類:

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

繫結介面卡

繫結介面卡負責發出相應的框架呼叫來設定值。例如,設定屬性值就像呼叫 setText() 方法一樣。再比如,設定事件監聽器就像呼叫 setOnClickListener() 方法。

資料繫結庫允許您通過使用介面卡指定為設定值而呼叫的方法、提供您自己的繫結邏輯,以及指定返回物件的型別

設定特性值

只要繫結值發生更改,生成的繫結類就必須使用繫結表示式在檢視上呼叫 setter 方法。您可以允許資料繫結庫自動確定方法、顯式宣告方法或提供選擇方法的自定義邏輯

自動選擇方法

對於名為 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:scrimColor 和 app: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 annotation 與 setter 相關聯。**annotation 與類一起使用,可以包含多個 BindingMethod annotation ,每個annotation 對應一個重新命名的方法。繫結方法是可新增到應用中任何類的annotation **。在以下示例中,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 annotation 的靜態繫結介面卡方法支援自定義特性 setter 的呼叫方式

Android 框架類的特性已經建立了 BindingAdapter 註釋。例如,以下示例展示了 paddingLeft 屬性的繫結介面卡:

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

引數型別非常重要。第一個引數用於確定與特性關聯的檢視型別,第二個引數用於確定在給定特性的繫結表示式中接受的型別

繫結介面卡對其他型別的自定義很有用。例如,可以通過工作器執行緒呼叫自定義載入程式來載入圖片。

出現衝突時,您定義的繫結介面卡會替換由 Android 框架提供的預設介面卡

您還可以使用接收多個屬性的介面卡,如以下示例所示:

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

您可以在佈局中使用介面卡,如以下示例所示。請注意,@drawable/venueError 引用應用中的資源。使用 @{} 將資源括起來可使其成為有效的繫結表示式

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

注意:資料繫結庫在匹配時會忽略自定義名稱空間
如果 ImageView 物件同時使用了 imageUrl 和 error,並且 imageUrl 是字串,error 是 Drawable,就會呼叫介面卡。如果您希望在設定了任意屬性時呼叫介面卡,則可以將介面卡的可選 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);
  }
}

注意:出現衝突時,繫結介面卡會替換預設的資料繫結介面卡

繫結介面卡方法可以選擇性在處理程式中使用舊值。同時獲取舊值和新值的方法應該先為屬性宣告所有舊值,然後再宣告新值,如以下示例所示:

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

事件處理指令碼只能與具有一種抽象方法的介面或抽象類一起使用,如以下示例所示:

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

當監聽器有多個方法時,必須將它拆分為多個監聽器。例如,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);
}

因為更改一個監聽器也會影響另一個監聽器,所以需要適用於其中一個屬性或同時適用於這兩個屬性的介面卡。您可以在註釋中將 requireAll 設定為 false,以指定並非必須為每個屬性都分配繫結表示式,如以下示例所示:

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

以上示例比一般情況稍微複雜一些,因為 View 類使用 addOnAttachStateChangeListener() 和 removeOnAttachStateChangeListener() 方法,而非 OnAttachStateChangeListener 的 setter 方法。android.databinding.adapters.ListenerUtil 類有助於跟蹤以前的監聽器,以便在繫結介面卡中將它們移除

通過用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 註釋介面 OnViewDetachedFromWindow 和 OnViewAttachedToWindow,資料繫結程式碼生成器知道只應在執行 Android 3.1(API 級別 12)及更高階別(addOnAttachStateChangeListener() 方法支援的相同版本)時生成監聽器

物件轉換

自動轉換物件

當繫結表示式返回 Object 時,庫會選擇用於設定屬性值的方法Object 會轉換為所選方法的引數型別。對於使用 ObservableMap 類儲存資料的應用,這種行為非常便捷,如以下示例所示:

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

注意:您還可以使用 object.key 表示法引用對映中的值。例如,以上示例中的 @{userMap[“lastName”]} 可替換為 @{userMap.lastName}。

表示式中的 userMap 物件會返回一個值,該值會自動轉換為用於設定 android:text 特性值的 setText(CharSequence) 方法中的引數型別。如果引數型別不明確,則必須在表示式中強制轉換返回型別。

自定義轉換

在某些情況下,需要在特定型別之間進行自定義轉換。例如,檢視的 android:background 特性需要 Drawable,但指定的 color 值是整數。以下示例展示了某個屬性需要 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);
}

但是,繫結表示式中提供的值型別必須保持一致。您不能在同一個表示式中使用不同的型別,如以下示例所示:

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

將佈局檢視繫結到架構元件

AndroidX 庫包含架構元件 (Architecture Components),可用於設計可靠、可測試且可維護的應用。資料繫結庫 (Data Binding Library) 可與架構元件無縫協作,進一步簡化介面的開發。應用中的佈局可以繫結到架構元件中的資料,這些元件已經可幫助您管理介面控制器生命週期並通知資料變化。

本頁介紹瞭如何將架構元件整合到您的應用中,以進一步凸顯使用資料繫結庫的優勢。

使用 LiveData 將資料變化通知給介面

您可以使用 LiveData 物件作為資料繫結來源,自動將資料變化通知給介面。如需詳細瞭解此架構元件,請參閱 LiveData 概覽

與實現 Observable 的物件(例如可觀察欄位)不同,LiveData 物件瞭解訂閱資料更改的觀察器的生命週期。瞭解這一點有許多好處,具體說明請參閱使用 LiveData 的優勢。在 Android Studio 版本 3.1 及更高版本中,您可以在資料繫結程式碼中將可觀察欄位替換為 LiveData 物件

要將 LiveData 物件與繫結類一起使用,您需要指定生命週期所有者來定義 LiveData 物件的範圍。以下示例在繫結類例項化後將 Activity 指定為生命週期所有者:

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);

        // Specify the current activity as the lifecycle owner.
        binding.setLifecycleOwner(this);
    }
}

可以根據使用 ViewModel 管理介面相關資料中所述,使用 ViewModel 元件來將資料繫結到佈局在 ViewModel 元件中,您可以使用 LiveData 物件轉換資料或合併多個資料來源。以下示例展示瞭如何在 ViewModel 中轉換資料:

class ScheduleViewModel extends ViewModel {
    LiveData username;

    public ScheduleViewModel() {
        String result = Repository.userName;
        userName = Transformations.map(result, result -> result.value);
    }
}

使用 ViewModel 管理介面相關資料

資料繫結庫可與 ViewModel 元件無縫協作,這類元件會公開佈局觀察到並對其變化做出響應的資料通過將 ViewModel 元件與資料繫結庫結合使用,您可以將介面邏輯從佈局移出,並移入到這些元件中,以便於測試資料繫結庫確保在需要時將檢視與資料來源繫結或解綁。大部分的其餘工作是為了確保您公開的是正確的資料。有關此架構元件的更多資訊,請參閱 ViewModel 概覽

要將 ViewModel 元件與資料繫結庫一起使用,必須例項化從 ViewModel 類繼承而來的元件,獲取繫結類的例項,並將您的 ViewModel 元件分配給繫結類中的屬性。以下示例展示瞭如何將元件與庫結合使用:

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Obtain the ViewModel component.
        UserModel userModel = new ViewModelProvider(this).get(UserModel.class);

        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);

        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel;
    }
}

在您的佈局中,使用繫結表示式將 ViewModel 元件的屬性和方法分配給對應的檢視,如以下示例所示:

<CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@{viewmodel.rememberMe}"
        android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />

使用 Observable ViewModel 更好地控制繫結介面卡

可以使用實現 Observable 的 ViewModel 元件,向其他應用元件發出資料變化通知,這與使用 LiveData 物件的方式類似

在某些情況下,您可能更願意使用實現 Observable 介面的 ViewModel 元件,而不是使用 LiveData 物件,即使這樣會失去對 LiveData 的生命週期管理功能也不影響使用實現 Observable 的 ViewModel 元件可讓您更好地控制應用中的繫結介面卡。例如,這種模式可讓您更好地控制資料更改時發出的通知,您還可以指定自定義方法來設定雙向資料繫結中的屬性值

如需實現可觀察的 ViewModel 元件,您必須建立一個從 ViewModel 類繼承而來並實現 Observable 介面的類。您可以使用 addOnPropertyChangedCallback() 和 removeOnPropertyChangedCallback() 方法提供觀察器訂閱或取消訂閱通知時的自定義邏輯。您還可以在 notifyPropertyChanged() 方法中提供屬性更改時執行的自定義邏輯。以下程式碼示例展示瞭如何實現一個可觀察的 ViewModel:

/**
 * A ViewModel that is also an Observable,
 * to be used with the Data Binding Library.
 */
class ObservableViewModel extends ViewModel implements Observable {
    private PropertyChangeRegistry callbacks = new PropertyChangeRegistry();

    @Override
    protected void addOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }

    @Override
    protected void removeOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    void notifyChange() {
        callbacks.notifyCallbacks(this, 0, null);
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    void notifyPropertyChanged(int fieldId) {
        callbacks.notifyCallbacks(this, fieldId, null);
    }
}

雙向資料繫結

使用單向資料繫結時,您可以為特性設定值,並設定對該特性的變化作出反應的監聽器

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>

雙向資料繫結為此過程提供了一種快捷方式

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"
/>

@={} 表示法(其中重要的是包含“=”符號)可接收屬性的資料更改並同時監聽使用者更新

為了對後臺資料的變化作出反應,您可以將您的佈局變數設定為 Observable(通常為 BaseObservable)的實現,並使用 @Bindable 註釋,如以下程式碼段所示:

public class LoginViewModel extends BaseObservable {
    // private Model data = ...

    @Bindable
    public Boolean getRememberMe() {
        return data.rememberMe;
    }

    public void setRememberMe(Boolean value) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value;

            // React to the change.
            saveData();

            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me);
        }
    }
}

由於可繫結屬性的 getter 方法稱為 getRememberMe(),因此屬性的相應 setter 方法會自動使用名稱 setRememberMe()。

有關 BaseObservable 和 @Bindable 用法的詳細資訊,請參閱使用可觀察的資料物件

使用自定義特性的雙向資料繫結

該平臺為最常見的雙向特性和更改監聽器提供了雙向資料繫結實現,您可以將其用作應用的一部分。如果您希望結合使用雙向資料繫結和自定義特性,則需要使用 @InverseBindingAdapter 和 @InverseBindingMethod 註釋

例如,如果要在名為 MyView 的自定義檢視中對 “time” 特性啟用雙向資料繫結,請完成以下步驟:

  1. 使用 @BindingAdapter,對用來設定初始值並在值更改時進行更新的方法進行註釋

    @BindingAdapter("time")
    public static void setTime(MyView view, Time newValue) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue;
        }
    }
    
  2. 使用 @InverseBindingAdapter 對從檢視中讀取值的方法進行註釋

    @InverseBindingAdapter("time")
    public static Time getTime(MyView view) {
        return view.getTime();
    }
    

此時,資料繫結知道在資料發生更改時要執行的操作(呼叫使用 @BindingAdapter 註釋的方法)以及當 view 視特性發生更改時要呼叫的內容(呼叫 InverseBindingListener)但是,它不知道特性何時或如何更改

為此,您需要在檢視上設定監聽器。這可以是與您的自定義檢視相關聯的自定義監聽器,也可以是通用事件,例如失去焦點或文字更改。將 @BindingAdapter 註釋新增到設定監聽器(用來監聽屬性更改)的方法中

@BindingAdapter("app:timeAttrChanged")
public static void setListeners(
        MyView view, final InverseBindingListener attrChange) {
    // Set a listener for click, focus, touch, etc.
}

該監聽器包含一個 InverseBindingListener 引數。您可以使用 InverseBindingListener 告知資料繫結系統,特性已更改。然後,該系統可以開始呼叫使用 @InverseBindingAdapter 註釋的方法,依此類推。

注意:每個雙向繫結都會生成“合成事件特性”。該特性與基本特性具有相同的名稱,但包含字尾 “AttrChanged”合成事件特性允許庫建立使用 @BindingAdapter 註釋的方法,以將事件監聽器與相應的 View 例項相關聯

實際上,此監聽器包含一些複雜邏輯,包括用於單向資料繫結的監聽器。用於文字屬性更改的介面卡 TextViewBindingAdapter 就是一個例子。

轉換器

如果繫結到 View 物件的變數需要設定格式、轉換或更改後才能顯示,則可以使用 Converter 物件

以顯示日期的 EditText 物件為例:

<EditText
    android:id="@+id/birth_date"
    android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>

viewmodel.birthDate 屬性包含 Long 型別的值,因此需要使用轉換器設定格式。

由於使用了雙向表示式,因此還需要使用反向轉換器,以告知庫如何將使用者提供的字串轉換回後備資料型別(在本例中為 Long)。此過程是通過向其中一個轉換器新增 @InverseMethod 註釋並讓此註釋引用反向轉換器來完成的。以下程式碼段顯示了此配置的一個示例:

public class Converter {
    @InverseMethod("stringToDate")
    public static String dateToString(EditText view, long oldValue,
            long value) {
        // Converts long to String.
    }

    public static long stringToDate(EditText view, String oldValue,
            String value) {
        // Converts String to long.
    }
}

使用雙向資料繫結的無限迴圈

使用雙向資料繫結時,請注意不要引入無限迴圈當使用者更改特性時,系統會呼叫使用 @InverseBindingAdapter 註釋的方法,並且該值將分配給後備屬性。繼而呼叫使用 @BindingAdapter 註釋的方法,從而觸發對使用 @InverseBindingAdapter 註釋的方法的另一個呼叫,依此類推

因此,通過比較使用 @BindingAdapter 註釋的方法中的新值和舊值,可以打破可能出現的無限迴圈

雙向特性

當您使用下表中的特性時,該平臺提供對雙向資料繫結的內建支援。有關平臺如何提供此類支援的詳細資訊,請參閱相應繫結介面卡的實現:

特性繫結介面卡
AdapterViewandroid:selectedItemPosition android:selectionAdapterViewBindingAdapter
CalendarViewandroid:dateCalendarViewBindingAdapter
CompoundButtonandroid:checkedCompoundButtonBindingAdapter
DatePickerandroid:year android:month android:dayDatePickerBindingAdapter
NumberPickerandroid:valueNumberPickerBindingAdapter
RadioButtonandroid:checkedButtonRadioGroupBindingAdapter
RatingBarandroid:ratingRatingBarBindingAdapter
SeekBarandroid:progressSeekBarBindingAdapter
TabHostandroid:currentTabTabHostBindingAdapter
TextViewandroid:textTextViewBindingAdapter
TimePickerandroid:hour android:minuteTimePickerBindingAdapter

相關文章