上一篇文章Android Data Binding——進(jìn)階
介紹了Data Binding的語法等進(jìn)階功能介褥。這一篇我們來介紹一下Data Binding的數(shù)據(jù)對象逾礁。
文中的例子可前往DataBindingDemo查看苞尝。
任何POJO對象都可以用在data binding中畸肆,但是對象改變時候,要如何通知UI更新呢宙址?這是使用Data Binding最奧妙的地方轴脐。Ps:我們這邊只是介紹如何使用,沒有涉及到實現(xiàn)原理抡砂。
有三種不同的數(shù)據(jù)變化通知機(jī)制:observable objects
, observable fields
, and observable collections
.
這些observable對象綁定到UI上大咱,當(dāng)對象的屬性更改時就會自動通知UI更新。
Observale Objects
一個繼承Observable
接口的類注益,data binding會設(shè)置一個listener用于監(jiān)聽綁定的對象的屬性變化碴巾。
public interface Observable {
/**
* Adds a callback to listen for changes to the Observable.
* @param callback The callback to start listening.
*/
void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
/**
* Removes a callback from those listening for changes.
* @param callback The callback that should stop listening.
*/
void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
/**
* The callback that is called by Observable when an observable property has changed.
*/
abstract class OnPropertyChangedCallback {
/**
* Called by an Observable whenever an observable property changes.
* @param sender The Observable that is changing.
* @param propertyId The BR identifier of the property that has changed. The getter
* for this property should be annotated with {@link Bindable}.
*/
public abstract void onPropertyChanged(Observable sender, int propertyId);
}
}
Observable
接口有注冊/刪除監(jiān)聽的方法,但是數(shù)據(jù)變化時是否通知取決于開發(fā)者丑搔。為了簡化開發(fā)厦瓢,data binding提供了一個BaseObservable
的基類,幫我們實現(xiàn)了監(jiān)聽的注冊和刪除啤月。這個類也實現(xiàn)了通知數(shù)據(jù)變化的方法煮仇,在getter
中使用Bindable
注解,在setter
中調(diào)用notifyPropertyChanged
通知數(shù)據(jù)變更谎仲。
public class ObservableUser extends BaseObservable {
public String firstName;
public String lastName;
public ObservableUser(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
@Bindable
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
Bindable
注解會在編譯時在BR中生成一個entry浙垫,當(dāng)數(shù)據(jù)變化時調(diào)用notifyPropertyChanged
通知這個entry數(shù)據(jù)發(fā)生了變化。
ObservableFields
創(chuàng)建Observable類還是比較麻煩的,data binding為我們提供了一個便捷的ObservableField
類以及它的派生類:
ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable
.
ObservableFields
是包含了一個單一屬性的observable objects夹姥,可以通過聲明一個public final field來使用它:
public class ObservableFieldUser extends BaseObservable {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public ObservableFieldUser(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
}
然后可以用set
/get
來存取數(shù)據(jù):
user.firstName.set("bai");
String s = user.firstName.get();
Observable Collections
有些應(yīng)用希望使用更加靈活的結(jié)構(gòu)來管理數(shù)據(jù)杉武,Observable集合類允許使用key來訪問這些數(shù)據(jù)對象。
- 如果key是String佃声,
ObservableArrayMap
會非常有用:
ObservableMap<String, String> userMap = new ObservableArrayMap<>();
userMap.put("firstName", "bai");
userMap.put("lastName", "li");
binding.setUserMap(userMap);
然后在布局文件中用String keys獲取map中的數(shù)據(jù):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.databinding.ObservableMap" />
<variable
name="userMap"
type="ObservableMap<String,String>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`obserable map : ` + userMap[`firstName`] + ` ` + userMap[`lastName`]}" />
</LinearLayout>
</layout>
- 如果key是integer,
ObservableArrayList
會非常有用:
ObservableList<User> useList = new ObservableArrayList<>();
useList.add(new User("bai", "li"));
binding.setUserList(useList);
然后在布局文件中使用下標(biāo)獲取list中的數(shù)據(jù):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.dragonjiang.databindingdemo.model.User" />
<import type="android.databinding.ObservableList" />
<variable
name="userList"
type="ObservableList<User>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`obserable list : ` + userList[0].toString()}" />
</LinearLayout>
</layout>
生成綁定
自動生成的Binding類都繼承了ViewDataBinding
類艺智,它們是連接layout的variables和Views的橋梁。
Creating
binding在View inflate之后創(chuàng)建圾亏。inflate方法會將Veiw綁定到binding上十拣,對于不同的Veiw有不同的創(chuàng)建方法:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局使用不同的機(jī)制inflate,可以單獨綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時候綁定不能提前確定志鹃,例如ListView的Item layout夭问,這時候可以使用DataBindingUtil
類:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
有ID的View
我們在之前的例子里面都沒有給View聲明一個id,因為用不到曹铃。但是如果有些情況下缰趋,我要調(diào)用到布局里面的特定的View,還是需要一個id陕见。data binding提供了一個比findViewById更快的機(jī)制:
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName + ` ` + user.lastName}" />
data binding會在binding類中自動生成對應(yīng)的屬性:
public final TextView tvName;
可以直接使用:
binding.tvName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(LayoutDetailsActivity.this, binding.tvName.getText(), Toast.LENGTH_SHORT).show();
}
});
ViewStubs
ViewStub
不同于正常的View秘血,它一開始是不可見的,在需要時才加載出特定的布局评甜。所以data binding提供了一個ViewStubProxy
類來代替ViewStub
,開發(fā)者可以通過這個類來操作ViewStub
灰粮。
ViewStub
需要在inflate時候創(chuàng)建一個binding,故需要設(shè)置監(jiān)聽ViewStub.OnInflateLister
:
public class ViewStubActivity extends AppCompatActivity {
private ActivityViewStubBinding mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
IncludeBinding binding = DataBindingUtil.bind(inflated);
binding.setUser(new User("bai", "li"));
}
});
}
public void onClick(View view) {
if (!mBinding.viewStub.isInflated()) {
mBinding.viewStub.getViewStub().inflate();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data></data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="inflate view_stub" />
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/include" />
</LinearLayout>
</layout>
高級綁定
有些情況下忍坷,例如RecyclerView.Adapter
中我們無法事先知道binding類粘舟。需要在onBindViewHodler(VH, int)
中給binding賦值。
在這種情況下佩研,RecyclerView布局內(nèi)都設(shè)置了一個item
變量柑肴,可以通過getBinding
方法返回一個ViewDataBinding
類:
public void onBindViewHolder(VH holder, int position) {
holder.binding.setModel(mDataList.get(position));
holder.binding.executePendingBindings();
}
注意到上面executePendingBindings()
表示立即綁定。如果沒有指定立即執(zhí)行旬薯,在數(shù)據(jù)變化時晰骑,binding會在下一幀開始前觸發(fā)。
屬性設(shè)置
當(dāng)綁定的數(shù)據(jù)變化時绊序,自動生成的binding類會尋找對應(yīng)屬性的setter方法些侍。data binding框架設(shè)置了幾種自定義賦值的機(jī)制。
自動Setter
對于一個屬性政模,data binding 嘗試找到對應(yīng)的setter方法岗宣,例如我們自定義了一個UserView
類,實現(xiàn)一個setUser
方法:
public class UserView extends AppCompatTextView {
public UserView(Context context) {
super(context);
}
public UserView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public UserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setUser(User user) {
this.setText(user.toString());
}
}
在布局文件中使用:
<com.dragonjiang.databindingdemo.ui.UserView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:user="@{user}" />
data binding自動為我們找到了setUser(User user)
的方法淋样。
重命名Setter
有的屬性的名稱與它的setter不匹配耗式,對于這類屬性,可以使用注解BindingMethods
將屬性與setter關(guān)聯(lián)起來。例如下面這個例子將andorid:tint
與setImageTintList
關(guān)聯(lián)起來:
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
自定義Setter(Binding Adapter)
有些屬性需要自定義屬性設(shè)置邏輯刊咳,例如沒有android:paddingLeft
屬性對應(yīng)的setter方法彪见。但是有setPadding(left, top, right, bottom)
。一個用BindingAdapter
注解的靜態(tài)方法允許開發(fā)者自定義setter:
@android.databinding.BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
BindingAdapter的方法還可以獲取舊的值娱挨。只需將舊的值放前面余指,新的值放后面:
@android.databinding.BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int padding) {
if (oldPadding != padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
BindingAdapter很強(qiáng)大,尤其對自定義屬性跷坝。比如可以用來異步加載圖片:
@android.databinding.BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Glide.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="@{@drawable/ic_launcher}"
app:imageUrl="@{user.avatar}" />
當(dāng)imageUrl
和error
屬性被使用時酵镜,就會匹配調(diào)用BindindAdapter的loadImage
方法。
轉(zhuǎn)換器
對象轉(zhuǎn)換
如果binding表達(dá)式返回一個對象柴钻,data binding會尋找對應(yīng)的setter(自動setter淮韭、重命名setter、自定義setter)贴届,然后將返回的對象強(qiáng)制轉(zhuǎn)換成setter需要的類型靠粪。
這是一個使用ObservableMap
的例子:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
userMap
返回一個對象,這個對象會被自動轉(zhuǎn)換為setText(CharSequence)
需要的類型毫蚓。如果類型轉(zhuǎn)換有問題占键,開發(fā)者需要受到進(jìn)行類型轉(zhuǎn)換。
自定義轉(zhuǎn)換
有時候需要對一些特定的類型直接做轉(zhuǎn)換元潘,例如設(shè)置背景:
<com.dragonjiang.databindingdemo.ui.UserView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@{user.isAdult ? @color/colorAccent : @color/colorPrimary}"
app:user="@{user}" />
這里background
需要Drawable
類型畔乙,而color是int類型,此時需要一個BindingConversation將int轉(zhuǎn)為ColorDrawable:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
注意:轉(zhuǎn)換只能在setter時生效柬批,所以不允許混合類型:
<View
<!--這是不允許的-->
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>