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