1. 程式人生 > >Android MVVM框架學習總結(二)

Android MVVM框架學習總結(二)

Android MVVM框架學習總結

4. 資料物件

任何POJO物件都可以用於資料繫結,但是更改POJO物件,並不會引起UI更新。有三種不同的資料更改通知機制:觀察物件,觀察欄位和觀察集合。當其中一個繫結到使用者介面的可觀察的資料物件,觀察到資料物件的屬性變化,使用者介面將自動更新。
(1)觀察物件(Observable Objects)
A class implementing the Observable interface ,將被允許附加一個listener,來監聽物件所有屬性的改變
Observable interface有一個機制來新增和刪除listeners,但需要通知開發者。為了讓開發變得更容易,提供一個基類,BaseObservable,它實現建立listener的註冊機制。當屬性資料改變時,資料類實現者需要響應通知。這是通過在getter上使用一個註解@Bindable,並在setter中進行通知。

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

@Bindable 在編譯時將生成一個BR class 。BR類檔案將生成在moudle的package下。如果資料類的基類不能被改變,Observable interface的實現類可以使用PropertyChangeRegistry儲存和有效地通知listeners。

(2)觀察屬性(ObservableFields)
如果想做比建立上面的觀察物件更少的工作,那麼可以使用ObservableField 和它的同級的一些型別: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。它們都是一個包含單一屬性的可觀察的物件。為避免裝箱、拆箱操作,可以在資料類中定義成 public final field …

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

如下,使用get或set來獲取和設定屬性值:

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

(3)觀察集合(Observable Collections)
一些應用程式使用更多的動態結構來儲存資料。可觀察集合允許通過key訪問這些資料物件。當key是一個引用型別,如String,就可以用ObservableArrayMap。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在下面的佈局中,就可以通過String型別的key來訪問map中的資料:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</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"/>

當key是一個Integer時,可用ObservableArrayList:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

佈局中使用ObservableArrayList:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</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"/>

5. 生成Binding

生成的繫結類,會連結佈局的variables。正如前面所討論的,繫結的名稱和包可能是自定義的的。生成的所有繫結類都 extends ViewDataBinding。
(1)建立
如前文所述,由相應的佈局檔案,而生成了相應的binding class。程式碼中使用它,需要LayoutInflater,如:

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

如果佈局已經inflate了,在某些場景時,可以單獨繫結:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時binding class 不能提前知道。在這種情況下,可以使用DataBindingUtil類建立繫結:

ViewDataBinding binding = DataBindingUtil.inflate(
        LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

(2)View中使用id
在佈局中的每個使用了id的view,都會在binding class中創建出一個同名的public屬性。這種繫結機制,比findViewById更快速。

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

在Activity或者Fragment中可以這樣引用,binding.firstName

(3)高階繫結
1)動態Variable
有時,特定的繫結類不會為人所知。例如,一個RecyclerView.Adapter操作的任意佈局,不知其特定的繫結類。仍需要通過onBindViewHolder(VH, int)來繫結值。
在下面的例子中,RecyclerView中的所有佈局,都綁定了一個”item”,有一個BindingHolder#getBinding() 返回ViewDataBinding 型別:

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

2)直接繫結
當一個variable or observable發生了改變,繫結框架會安排在下一幀進行檢視的改變。然而,有時希望立即發生改變。可以使用executePendingBindings()來強制執行

3)後臺執行緒
你可以改變你的資料模型在一個後臺執行緒,只要它不是一個集合。資料繫結框架將本地化每個變數和屬性,以避免任何併發問題。

6.設定屬性

當view使用了繫結表示式,只要繫結值發生變化,生成的繫結類必須呼叫相應的setter方法。定製資料繫結框架的方式方法呼叫設定值。資料繫結框架允許自定義setter方法。
自動Setters
對於一個attribute,資料繫結框架將試圖查詢對應的setAttribute()。它的namespace並不重要,只關注attribute的name。如,TextView中的屬性android:text使用了表示式,那麼框架就會查詢setText(String)。如果表示式返回的是int,那麼將會查詢setText(int)。所以,表示式需要返回正確的型別。資料繫結框架,支援建立一個佈局元素(View|ViewGroup)中,並不存在的屬性。
如下,生成的binding class中,將生成一個setDrawerListener(listener):

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

自定義Setters
一些屬性需要自定義繫結邏輯。例如,屬性android:paddingLeft沒有對應的setter方法,而在view中有一個方法為setPadding(left, top, right, bottom)。可以使用@BindingAdapter來自定義一個關於屬性android:paddingLeft的setter。例:

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

還可以用Binding adapter來接收多個引數:

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

對應的xml繫結:

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

當ImageView中同時使用了屬性String imageUrl和Drawlable error,通過Binding adapter,這時的setter就是 loadImage()。

7.轉換器

物件轉換
當從一個繫結表示式返回一個物件,就會有一個setter被採用。該物件將會被轉換成setter中的引數型別。
下面的例子,通過ObservableMaps來儲存資料:

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

userMap返回一個物件,該物件將自動轉換成setText(CharSequence)中的引數型別。可能會有混亂的引數型別,開發人員需要在表示式中顯式cast

自定義轉換
有時特定型別之間會自動轉換。例如,設定背景:

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

這裡,background的setter的引數型別應該是一個drawable,但color是一個整數。應當有一個轉換規則,將int color轉換為ColorDrawable。這種轉換是通過使用一個@BindingConversion的靜態方法來實現的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

注意,轉換隻發生在setter時期。所以不允許混合型別,如:

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