DataBinding最全使用說明
Google開源的數(shù)據(jù)綁定框架, 實現(xiàn)了MVVM架構(gòu), 增強(qiáng)了xml的功能, 大幅度精簡了java代碼量, 并且代碼可讀性更高, 對性能的影響基本為零.
DataBinding會自動在build目錄下生成類. 因為被集成進(jìn)AndroidStudio所以不需要你手動編譯會實時編譯, 并且支持大部分代碼補(bǔ)全.
啟用DataBinding
android{
dataBinding {
enabled = true;
}
}
復(fù)制代碼
因為怕你們沒注意到我寫在文章開頭
- 我想強(qiáng)調(diào)的是XML只做賦值或者簡單的三元運(yùn)算或者判空等不要做復(fù)雜運(yùn)算;
- 邏輯運(yùn)算在Model中
- 有時候可以偷懶將Activity當(dāng)作ViewModel來使用
DataBinding的強(qiáng)大是毋庸置疑, 只會更方便(拋棄MVP吧);
鑒于文章篇幅, 后面我將會出一篇文章以及開源庫告訴大家如何實現(xiàn)DataBinding是如何讓RecyclerView一行代碼寫通用適配器(無需寫實現(xiàn)類)
一行代碼實現(xiàn)多類型/添加頭布局腳布局/點擊事件;
[圖片上傳失敗...(image-313607-1554879196408)]
布局
布局文件
<layout>
<data>
<variable
name="user"
type="com.liangjingkanji.databinding.pojo.UserBean"/>
</data>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.liangjingkanji.databinding.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.userName}"
/>
</RelativeLayout>
</layout>
復(fù)制代碼
layout
布局根節(jié)點必須是<layout>
. 同時layout只能包含一個View標(biāo)簽. 不能直接包含<merge>
data
<data>
標(biāo)簽的內(nèi)容即DataBinding的數(shù)據(jù). data標(biāo)簽只能存在一個.
variable
通過<variable>
標(biāo)簽可以指定類, 然后在控件的屬性值中就可以使用
<data>
<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
復(fù)制代碼
通過DataBinding的setxx()
方法可以給Variable設(shè)置數(shù)據(jù). name值不能包含_
下劃線
import
第二種寫法(導(dǎo)入), 默認(rèn)導(dǎo)入了java/lang
包下的類(String/Integer). 可以直接使用被導(dǎo)入的類的靜態(tài)方法.
<data>
<!--導(dǎo)入類-->
<import type="com.liangfeizc.databindingsamples.basic.User" />
<!--因為User已經(jīng)導(dǎo)入, 所以可以簡寫類名-->
<variable name="user" type="User" />
</data>
復(fù)制代碼
使用類
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.userName}"
/>
<!--user就是在Variable標(biāo)簽中的name, 可以隨意自定義, 然后就會使用type中的類-->
復(fù)制代碼
Tip: user
代表UserBean這個類, 可以使用UserBean中的方法以及成員變量. 如果是getxx()
會自動識別為xx
. 注意不能使用字符串android
, 否則會報錯無法綁定.
class
<data>
標(biāo)簽有個屬性<class>
可以自定義DataBinding生成的類名以及路徑
<!--自定義類名-->
<data class="CustomDataBinding"></data>
<!--自定義生成路徑以及類型-->
<data class=".CustomDataBinding"></data> <!--自動在包名下生成包以及類-->
復(fù)制代碼
Tip:注意沒有代碼自動補(bǔ)全. 自定義路徑Module/build/generated/source/apt/debug/databinding/
目錄下, 基本上不需要自定義路徑
默認(rèn):
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ActivityMainBinding這個類根據(jù)布局文件名生成(id+Binding)
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserBean userBean = new UserBean();
userBean.setUserName("姜濤");
// setUser這個方法根據(jù)Variable標(biāo)簽的name屬性自動生成
viewDataBinding.setUser(userBean);
}
}
復(fù)制代碼
alias
<variable>
標(biāo)簽如果需要導(dǎo)入(import)兩個同名的類時可以使用alias
屬性(別名屬性)
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
復(fù)制代碼
include
在include其他布局的時候可能需要傳遞變量(variable)值過去
<variable
name="userName"
type="String"/>
....
<include
layout="@layout/include_demo"
bind:userName="@{userName}"/>
復(fù)制代碼
include_demo
<data>
<variable
name="userName"
type="String"/>
</data>
...
android:text="@{userName}"
復(fù)制代碼
兩個布局通過include
的bind:<變量名>
值來傳遞. 而且兩者必須有同一個變量
DataBinding不支持merge標(biāo)簽
自動布局屬性
DataBinding對于自定義屬性支持非常好, 只要View中包含setter方法就可以直接在布局中使用該屬性
public void setCustomName(@NonNull final String customName) {
mLastName.setText("吳彥祖");
}
復(fù)制代碼
然后直接使用(但是IDE沒有代碼補(bǔ)全)
app:customName="@{@string/wuyanzu}"
復(fù)制代碼
但是setter方法只支持單個參數(shù). app:
這個命名空間可以隨意
數(shù)據(jù)雙向綁定
視圖跟隨數(shù)據(jù)刷新
BaseObservable
如果需要數(shù)據(jù)變化是視圖也跟著變化則需要使用到以下兩種方法
有兩種方式:
-
繼承BaseObservable
public class ObservableUser extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return firstName; } // 注解才會自動在build目錄BR類中生成entry, 要求方法名必須以get開頭 @Bindable public String getLastName() { return lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); // 需要手動刷新 } } 復(fù)制代碼
還可以監(jiān)聽屬性改變事件
ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
}
});
復(fù)制代碼
屬性第一次改變時會回調(diào)兩次, 之后都只回調(diào)一次. 如果使用notifyChange()
不會得到id(即i等于0). 使用
notifyPropertyChanged(i)
就可以在回調(diào)里面得到id.
BaseObservable和Observable的區(qū)別:
- BaseObservable是實現(xiàn)了Observable的類, 幫我們實現(xiàn)了監(jiān)聽器的線程安全問題.
- BaseObservable使用了PropertyChangeRegistry來執(zhí)行OnPropertyChangedCallback
- 所以我不推薦你直接實現(xiàn)Observable.
ObservableField
databinding默認(rèn)實現(xiàn)了一系列實現(xiàn)Observable接口的字段類型
BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
復(fù)制代碼
示例
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
復(fù)制代碼
對于集合數(shù)據(jù)類型ObservableArrayMap/ObservableArrayLis/ObjservableMap
等集合數(shù)據(jù)類型
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
復(fù)制代碼
使用
<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"/>
復(fù)制代碼
Tip:
- 還支持
ObservableParcelable<Object>
序列化數(shù)據(jù)類型 - 上面說的這兩種只會視圖跟隨數(shù)據(jù)更新, 數(shù)據(jù)并不會跟隨視圖刷新.
- ObservableField同樣支持addOnPropertyChangedCallback監(jiān)聽屬性改變
數(shù)據(jù)跟隨視圖刷新
通過表達(dá)式使用@=
表達(dá)式就可以視圖刷新的時候自動更新數(shù)據(jù), 但是要求數(shù)據(jù)實現(xiàn)以下兩種方式修改才會觸發(fā)刷新
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions"
android:text="@={model.name}"/>
復(fù)制代碼
這種雙向綁定存在一個很大的問題就是會死循環(huán). 數(shù)據(jù)變化(回調(diào)監(jiān)聽器)觸發(fā)視圖變化, 然后視圖又會觸發(fā)數(shù)據(jù)變化(再次回調(diào)監(jiān)聽器), 然后一直循環(huán), 設(shè)置相同的數(shù)據(jù)也視為數(shù)據(jù)變化.
所以我們需要判斷當(dāng)前變化的數(shù)據(jù)是否等同于舊數(shù)據(jù)
public class CustomBindingAdapter {
@BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
CharSequence oldText = view.getText();
if (!haveContentsChanged(text, oldText)) {
return; // 數(shù)據(jù)沒有變化不進(jìn)行刷新視圖
}
view.setText(text);
}
// 本工具類截取自官方源碼
private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
if ((str1 == null) != (str2 == null)) {
return true;
} else if (str1 == null) {
return false;
}
final int length = str1.length();
if (length != str2.length()) {
return true;
}
for (int i = 0; i < length; i++) {
if (str1.charAt(i) != str2.charAt(i)) {
return true;
}
}
return false;
}
}
復(fù)制代碼
Tip:
根據(jù)我上面說的, 監(jiān)聽器至少回調(diào)兩次(數(shù)據(jù)->視圖, 視圖-> 數(shù)據(jù))
-
以下這種是無效的, 因為String參數(shù)傳遞屬于引用類型變量并不是常量, 需要用
equals()
// 本段截取官方源碼, 我也不知道這sb為什么這么寫 if (text == oldText || (text == null && oldText.length() == 0)) { return; } /**/ 復(fù)制代碼
正確
if (text == null || text.equals(oldText) || oldText.length() == 0) { return; } 復(fù)制代碼
總結(jié)就是如果沒有默認(rèn)實行的控件屬性使用雙向數(shù)據(jù)綁定 就需要你自己實現(xiàn)BindingAdapter注解
注解
@Bindable
用于數(shù)據(jù)更新自動刷新視圖. 后面提.
@BindingAdapter
用于標(biāo)記方法. 前面提到了DataBinding自定義屬性自動識別setter.
如果我們需要自定義xml, 就需要修改View的源碼 ,但是DataBinding還有第二種方法相當(dāng)于可以將setter方法抽取出來, 并且同時支持多個屬性.
圖片加載框架可以方便使用此方法.
@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
public static void loadImage(ImageView view, String url, Drawable error) {
Glide.with(view.getContext()).load(url).into(view);
}
復(fù)制代碼
- 修飾方法, 要求方法必須
public static
- 方法參數(shù)第一個要求必須是View
- 方法名不作要求
- 最后這個boolean類型是可選參數(shù). 可以要求是否所有參數(shù)都需要填寫. 默認(rèn)true.
- 如果requireAll為false, 你沒有填寫的屬性值將為null. 所以需要做非空判斷.
使用:
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
app:error="@{@drawable/error}"
wuyanzu:imageUrl="@{imageUrl}"
app:onClickListener="@{activity.avatarClickListener}"
/>
復(fù)制代碼
可以看到命名空間可以隨意, 但是如果在BindingAdapter的數(shù)組內(nèi)你定義了命名空間就必須完全遵守
例如:
// 這里省略了一個注解參數(shù).
@BindingAdapter({ "android:imageUrl", "error" })
public static void loadImage(ImageView view, String url, Drawable error) {
if(url == null) return;
Glide.with(view.getContext()).load(url).into(view);
}
復(fù)制代碼
Tip: 如果你的數(shù)據(jù)初始化是在異步的. 會回調(diào)方法但是數(shù)據(jù)為null(成員默認(rèn)值). 所以我們必須要首先進(jìn)行判空處理.
@BindingMethods
DataBinding默認(rèn)可以在布局中使用setter方法作為自定義屬性, 但是如果不是setter格式的方法就要使用BindingMethod注解了. 通過創(chuàng)建一個自定義屬性來關(guān)聯(lián)一個類中已有的方法.
該注解屬于一個容器. 內(nèi)部參數(shù)是一個@BindingMethod數(shù)組, 只能用于修飾類(任意類都可以, 類可以為空)
官方示例:
@BindingMethods({
@BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"),
@BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"),
@BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"),
})
public class ProgressBarBindingAdapter {
}
復(fù)制代碼
@BindingMethod
該注解必須有三個屬性
- type: 字節(jié)碼
- attribute: 屬性
- method: 方法
會在指定的字節(jié)碼(type)中尋找方法(method), 然后通過你創(chuàng)建的布局屬性(Attribute)來回調(diào)方法
如果屬性名和@BindingAdapter沖突會報錯
Tip: 可以注意到該注解只是單純地關(guān)聯(lián)已有的方法, 并不能新增方法. 所以全都是注解的空類.
@BindingConversion
屬性值自動進(jìn)行類型轉(zhuǎn)換
- 只能修飾
public static
方法. - 任意位置任意方法名都不限制
- DataBinding自動匹配被該注解修飾的方法和匹配參數(shù)類型
- 返回值類型必須和屬性setter方法匹配, 且參數(shù)只能有一個
- 要求屬性值必須是
@{}
DataBinding表達(dá)式
官方示例:
public class Converters {
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
@BindingConversion
public static ColorStateList convertColorToColorStateList(int color) {
return ColorStateList.valueOf(color);
}
}
復(fù)制代碼
設(shè)置布局中TextView的背景,
android:background="@{`吳彥祖`}"
復(fù)制代碼
可以看到我給背景隨意設(shè)置一個字符串. 這樣就不會匹配Background
的int參數(shù)類型. 然后DataBinding就會檢索匹配該類型的@BindingConversion
方法. 然后轉(zhuǎn)換.
注意android:text如果想用int自動轉(zhuǎn)String是不可以的, 因為int值會被識別為resource id. @BindingConversion無法工作.
@InverseMethod
在android studio3.0提供inverse系列的新注解, 全部都是針對數(shù)據(jù)雙向綁定.
在數(shù)據(jù)和視圖的數(shù)據(jù)不統(tǒng)一時可以使用該注解@InverseMethod
解決數(shù)據(jù)轉(zhuǎn)換的問題
例如數(shù)據(jù)模型存儲用戶的id但是視圖不顯示id而是顯示用戶名(數(shù)據(jù)和視圖的類型不一致), 我們就需要在兩者之間轉(zhuǎn)換.
需要創(chuàng)建public static
兩個方法, 我們簡稱為"轉(zhuǎn)換方法(convertion method)"和"反轉(zhuǎn)方法(inverse method)"
- 轉(zhuǎn)換方法與反轉(zhuǎn)方法的參數(shù)數(shù)量必須相同
- 轉(zhuǎn)換方法的最終參數(shù)的類型與反轉(zhuǎn)方法的返回值必須相同
轉(zhuǎn)換方法: 是刷新視圖的時候使用 (決定視圖顯示數(shù)據(jù)) 會回調(diào)兩次(文章后面詳細(xì)解釋雙向綁定的時候可以知道為何)
反轉(zhuǎn)方法: 是刷新數(shù)據(jù)的時候使用 (決定實體存儲數(shù)據(jù))
簡單示例:
在用戶id和用戶名之間轉(zhuǎn)換. 存儲id但是顯示的時候顯示用戶名
@InverseMethod("toID") public static String toName(TextView view, int id) {
if (id == 1) {
return "吳彥祖";
}
return "";
}
public static int toID(TextView view, String name) {
if (name.equals("吳彥祖")) {
return 1;
}
return 0;
}
復(fù)制代碼
使用
<TextView
android:id="@+id/iv"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="@={MyInverseMethod.toName( iv, data.id)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
復(fù)制代碼
注意和BindingAdapter不同, 參數(shù)有View表達(dá)式就必須加上View的id
Tip:
在這個注解之前其實都是通過修改實體的setter和getter方法達(dá)到類型的轉(zhuǎn)換. 但是這樣會侵入整個實體類
我使用的gson類都是自動生成的我并不想去手動修改任何方法.
@InverseBindingAdapter
參數(shù):
- String attribute 屬性值(必填)
- String event 非必填, 默認(rèn)值 屬性值 +
AttrChanged
后綴
介紹
- 作用于方法混埠,方法須為公共靜態(tài)方法鞍匾。
- 方法的第一個參數(shù)必須為View類型
- 必須與@BindingAdapter配合使用
event: 這個屬性存在默認(rèn)值(上面提過默認(rèn)值的生成規(guī)則), 我們需要創(chuàng)建@BindingAdapter
方法來實現(xiàn)event的屬性. 這個方法我暫且稱為數(shù)據(jù)變更方法
.
在你綁定DataBinding時候回自動調(diào)用這個數(shù)據(jù)變更方法
, (這個數(shù)據(jù)變更方法創(chuàng)建的屬性并不能在xml中使用)
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
復(fù)制代碼
給android:text
屬性使用@={}
雙向綁定表達(dá)式. 數(shù)據(jù)變化觸發(fā)視圖刷新是回調(diào)setter方法
數(shù)據(jù)變更方法(官方源碼簡化版):
@BindingAdapter(value = {
"android:textAttr"
}, requireAll = false)
public static void setTextWatcher(TextView view, final InverseBindingListener textAttrChanged) {
// 創(chuàng)建一個文字變化監(jiān)聽器
final TextWatcher newValue;
// 如果全部為null不要監(jiān)聽器
if (textAttrChanged == null) {
newValue = null;
} else {
newValue = new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
if (textAttrChanged != null) {
// 通知刷新
textAttrChanged.onChange();
}
}
@Override public void afterTextChanged(Editable s) {
}
};
}
// 如果視圖已經(jīng)有一個監(jiān)聽器就先刪除
final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
if (oldValue != null) {
view.removeTextChangedListener(oldValue);
}
// 給視圖添加監(jiān)聽器
if (newValue != null) {
view.addTextChangedListener(newValue);
}
}
復(fù)制代碼
這里用到一個InverseBindingListener
public interface InverseBindingListener {
/**
* Notifies the data binding system that the attribute value has changed.
*/
void onChange();
}
復(fù)制代碼
總結(jié)就是你只要通知
@InverseBindingMethods
類似BindingMethods. 參數(shù)是@InverseBindingMethod
如果說BindingMethods是關(guān)聯(lián)setter方法和自定義屬性, 那么InverseBindingMethods就是關(guān)聯(lián)getter方法和自定義屬性.
setter
是更新視圖的時候使用, 而getter
方法是更新數(shù)據(jù)時候使用的
必須與@BindingAdapter配合使用
- 修飾類
示例:
@InverseBindingMethods({
@InverseBindingMethod(type = RadioGroup.class, attribute = "android:checkedButton", method = "getCheckedRadioButtonId"),
})
public class RadioGroupBindingAdapter {
@BindingAdapter("android:checkedButton")
public static void setCheckedButton(RadioGroup view, int id) {
if (id != view.getCheckedRadioButtonId()) {
view.check(id);
}
}
復(fù)制代碼
@InverseBindingMethod
參數(shù):
Class type 控件的字節(jié)碼
String attribute 屬性
String event 默認(rèn)值是屬性加
AttrChanged
后綴作為默認(rèn)值String method Attribute值的getter形式作為默認(rèn)值通過屬性指定變化監(jiān)聽和返回方法
在自動生成DataBinding代碼中可以看到
private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of data.name
// is data.setName((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv); // 拿到變化的屬性
// localize variables for thread safety
// data != null
boolean dataJavaLangObjectNull = false;
// data.name
java.lang.String dataName = null;
// data
com.liangjingkanji.databinding.Bean data = mData; // 拿到數(shù)據(jù)
dataJavaLangObjectNull = (data) != (null);
if (dataJavaLangObjectNull) {
data.setName(((java.lang.String) (callbackArg_0))); // 存儲到數(shù)據(jù)
}
}
};
復(fù)制代碼
所以如果你沒用重寫Inverse的數(shù)據(jù)變更方法
將無法讓視圖通知數(shù)據(jù)刷新.
// 該方法會在綁定布局的時候回調(diào)
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String dataName = null;
com.liangjingkanji.databinding.Bean data = mData;
if ((dirtyFlags & 0x1aL) != 0) {
if (data != null) {
// read data.name
dataName = data.getName();
}
}
// batch finished
if ((dirtyFlags & 0x1aL) != 0) {
// api target 1
com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
// 重點是這段代碼, 將上面創(chuàng)建的監(jiān)聽器傳入setTextWatcher方法
com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr);
}
}
復(fù)制代碼
總結(jié)
@BindingBuildInfo
和@Untaggable
這兩個注解是DataBinding自動生成Java類時使用的.
-
Bindable
設(shè)置數(shù)據(jù)刷新視圖. 自動生成BR的ID
-
BindingAdapter
設(shè)置自定義屬性. 可以覆蓋系統(tǒng)原有屬性
-
BindingMethod/BindingMethods
關(guān)聯(lián)自定義屬性到控件原有的setter方法
-
BindingConversion
如果屬性不能匹配類型參數(shù)將自動根據(jù)類型參數(shù)匹配到該注解修飾的方法來轉(zhuǎn)換
-
InverseMethod
負(fù)責(zé)實現(xiàn)視圖和數(shù)據(jù)之間的轉(zhuǎn)換
-
InverseBindingAdapter
視圖通知數(shù)據(jù)刷新的
-
InverseBindingMethod/InverseBindingMethods
視圖通知數(shù)據(jù)刷新的(如果存在已有g(shù)etter方法可用的情況下)
建議參考官方實現(xiàn)源碼:
表達(dá)式
@{}
里面除了可以執(zhí)行方法以外還可以寫表達(dá)式, 并且支持一些特有表達(dá)式
- 算術(shù) + - / * %
- 字符串合并 +
- 邏輯 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比較 == > < >= <=
- Instanceof
- Grouping ()
- 文字 - character, String, numeric, null
- Cast
- 方法調(diào)用
- Field 訪問
- Array 訪問 []
- 三元 ?:
避免空指針
variable的值即使設(shè)置null或者沒有設(shè)置也不會出現(xiàn)空指針異常.
這是因為官方已經(jīng)用DataBinding的@BindingAdapter注解重寫了很多屬性. 并且里面進(jìn)行了判空處理.
<variable
name="userName"
type="String"/>
.....
android:text="@{userName}"
復(fù)制代碼
不會出現(xiàn)空指針異常.
dataBinding.setUserName(null);
復(fù)制代碼
并且還支持特有的非空多元表達(dá)式
android:text="@{user.displayName ?? user.lastName}"
復(fù)制代碼
就等價于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
復(fù)制代碼
還是需要注意數(shù)組越界的
集合
集合不屬于java.lang*
下, 需要導(dǎo)入全路徑.
<variable
name="list"
type="java.util.List<String>"/>
<variable
name="map"
type="java.util.Map<String, String>"/>
復(fù)制代碼
上面這種寫法會報錯
Error:與元素類型 "variable" 相關(guān)聯(lián)的 "type" 屬性值不能包含 '<' 字符。
復(fù)制代碼
因為<
符號需要轉(zhuǎn)義.
常用轉(zhuǎn)義字符
? 空格  避消;  ;
< 小于號 <型凳; <;
大于號 >; >嗦董;
& 與號 &; &瘦黑; " 引號 "展懈; "; ‘ 撇號 &apos供璧; '存崖; × 乘號 ×; ×睡毒; ÷ 除號 ÷来惧; ÷;
正確寫法
<variable
name="list"
type="java.util.List<String>"/>
<variable
name="map"
type="java.util.Map<String, String>"/>
復(fù)制代碼
集合和數(shù)組都可以用[]
來得到元素
android:text="@{map["firstName"]}"
復(fù)制代碼
字符串
如果想要在@{}
中使用字符串, 可以使用三種方式
第一種:
android:text='@{"吳彥祖"}'
復(fù)制代碼
第二種:
android:text="@{`吳彥祖`}"
復(fù)制代碼
第三種:
android:text="@{@string/user_name}"
復(fù)制代碼
同樣支持@color或@drawable
格式化字符串
首先在strings中定義<string>
<string name="string_format">名字: %s 性別: %s</string>
復(fù)制代碼
然后就可以使用DataBinding表達(dá)式
android:text="@{@string/string_format(`吳彥祖`, `男`)}"
復(fù)制代碼
輸出內(nèi)容:
名字: 吳彥祖 性別: 男
復(fù)制代碼
默認(rèn)值
如果Variable還沒有復(fù)制就會使用默認(rèn)值顯示.
android:text="@{user.integral, default=`30`}"
復(fù)制代碼
上下文
DataBinding本身提供了一個名為context的Variable. 可以直接使用. 等同于View的getContext()
.
android:text="@{context.getApplicationInfo().toString()}"
復(fù)制代碼
引用其他控件
<TextView
android:id="@+id/datingName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/iv_dating"
android:text="活動"
/>
/...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/iv_order"
android:text="@{datingName.text}"
/>
復(fù)制代碼
引用包含
_
的控件id是可以直接忽略該符號. 例如tv_name
直接寫tvName
.謝謝 lambda 指出錯誤
不論順序都可以引用
使用Class
如果想用Class作為參數(shù)傳遞, 那么該Class不能直接通過靜態(tài)導(dǎo)入來使用. 需要作為字段常量來使用
事件綁定
事件綁定分為兩種:
- 方法引用
- 監(jiān)聽綁定
對于默認(rèn)的事件需要書寫同樣的參數(shù)的方法才能接受到, 否則報錯. 例如onClick()方法必須有View參數(shù).
方法引用
public class MyHandlers {
// 注意必須要傳View參數(shù)
public void onClickFriend(View view) { ... }
}
復(fù)制代碼
直接通過View的屬性來調(diào)用類方法
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="activity"
type="com.liangjingkanji.databinding.MainActivity"/>
</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="@{activit::click}"/>
</LinearLayout>
</layout>
復(fù)制代碼
Tip: activity.click
和activity::click
都屬于方法調(diào)用, 但是如果是activity.click()
就會報錯. 因為對于默認(rèn)事件需要統(tǒng)一參數(shù). 必須加上activity.click(View v)
監(jiān)聽綁定
上面提到的都不能向回調(diào)里面?zhèn)鬟f自定義參數(shù). 而如果使用
android:onClick="@{()->activity.click(text)}"
復(fù)制代碼
就可以自定義回調(diào)參數(shù)了
ActivityMainBinding dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
dataBinding.setActivity(this);
dataBinding.setText("吳彥祖"); // 順序無所謂
復(fù)制代碼
然后在布局文件中使用Lambda
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="text"
type="String"/>
<variable
name="activity"
type="com.liangjingkanji.databinding.MainActivity"/>
</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="@{()->activity.click(text)}" />
</LinearLayout>
</layout>
復(fù)制代碼
注意: DataBinding會將控件所有的setter方法全部暴露為xml屬性. 是不是很方便??
DataBinding組件
ViewDataBinding
自動生成的DataBinding類都繼承自該類. 所以都擁有該類的方法
void addOnRebindCallback(OnRebindCallback listener)
// 添加綁定監(jiān)聽器, 可以在Variable被設(shè)置的時候回調(diào)
void removeOnRebindCallback(OnRebindCallback listener)
// 刪除綁定監(jiān)聽器
View getRoot()
// 返回被綁定的視圖對象
abstract void invalidateAll()
// 使所有的表達(dá)式無效并且立刻重新設(shè)置表達(dá)式. 會重新觸發(fā)OnRebindCallback回調(diào)(可以看做重置)
abstract boolean setVariable(int variableId, Object value)
// 可以根據(jù)字段id來設(shè)置變量
void unbind()
// 解綁布局, ui不會根據(jù)數(shù)據(jù)來變化, 但是監(jiān)聽器還是會觸發(fā)的
復(fù)制代碼
這里有三個方法需要重點講解:
abstract boolean hasPendingBindings()
// 當(dāng)ui需要根據(jù)當(dāng)前數(shù)據(jù)變化時就會返回true(數(shù)據(jù)變化后有一瞬間)
void executePendingBindings()
// 強(qiáng)制ui立刻刷新數(shù)據(jù),
復(fù)制代碼
當(dāng)你改變了數(shù)據(jù)以后(在你設(shè)置了Observable觀察器的情況下)會馬上刷新ui, 但是會在下一幀才會刷新UI, 存在一定的延遲時間. 在這段時間內(nèi)hasPendingBindings()
會返回true. 如果想要同步(或者說立刻)刷新UI可以馬上調(diào)用executePendingBindings()
.
OnRebindCallback:
該監(jiān)聽器可以監(jiān)聽到布局綁定的生命周期
mDataBinding.addOnRebindCallback(new OnRebindCallback() {
/**
* 綁定之前
* @param binding
* @return 如果返回true就會綁定布局, 返回false則取消綁定
*/
@Override public boolean onPreBind(ViewDataBinding binding) {
return false;
}
/**
* 如果取消綁定則回調(diào)該方法(取決于onPreBind的返回值)
* @param binding
*/
@Override public void onCanceled(ViewDataBinding binding) {
super.onCanceled(binding);
}
/**
* 綁定完成
* @param binding
*/
@Override public void onBound(ViewDataBinding binding) {
super.onBound(binding);
}
});
復(fù)制代碼
DataBinding也有個數(shù)據(jù)變更監(jiān)聽器, 可以監(jiān)聽Variable的設(shè)置事件
mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
/**
* 會在DataBinding設(shè)置數(shù)據(jù)的時候回調(diào)
* @param sender DataBinding生成的類
* @param propertyId Variable的id
*/
@Override public void onPropertyChanged(Observable sender, int propertyId) {
ActivityMainBinding databinding = (ActivityMainBinding) sender;
switch (propertyId) {
case BR.data:
Log.d("日志", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
break;
case BR.dataSecond:
break;
}
}
});
復(fù)制代碼
DataBindingUtil
DataBinding不僅可以綁定Activity還可以綁定視圖內(nèi)容(View)
// 視圖
static <T extends ViewDataBinding> T bind(View root)
static <T extends ViewDataBinding> T bind(View root,
DataBindingComponent bindingComponent)
// 布局
static <T extends ViewDataBinding> T inflate(LayoutInflater inflater,
int layoutId,
ViewGroup parent,
boolean attachToParent, DataBindingComponent bindingComponent) // 組件
static <T extends ViewDataBinding> T inflate(LayoutInflater inflater,
int layoutId,
ViewGroup parent,
boolean attachToParent)
// activity
static <T extends ViewDataBinding> T setContentView(Activity activity,
int layoutId)
static <T extends ViewDataBinding> T setContentView(Activity activity,
int layoutId, DataBindingComponent bindingComponent)
復(fù)制代碼
還有兩個不常用的方法, 檢索視圖是否被綁定, 如果沒有綁定返回nul
static <T extends ViewDataBinding> T getBinding(View view)
// 和getBinding不同的是如果視圖沒有綁定會去檢查父容器是否被綁定
static <T extends ViewDataBinding> T findBinding(View view)
復(fù)制代碼
其他的方法
// 根據(jù)傳的BR的id來返回字符串類型. 可能用于日志輸出
static String convertBrIdToString(int id)
復(fù)制代碼
例如BR.name這個字段對應(yīng)的是4, 就可以使用該方法將4轉(zhuǎn)成"name"
DataBindingComponent
每個DataBinding都可以擁有一個組件或者說設(shè)置一個默認(rèn)的全局組件
創(chuàng)建一個Component的步驟:
- 創(chuàng)建一個MyDataBindingComponent實現(xiàn)接口DataBindingComponent
- 創(chuàng)建MyBindingAdapter類, 用@BindingAdapter修飾其成員方法(不需要靜態(tài))
- 在MyDataBindingComponent寫入一個get**方法()來返回該MyBindingAdapter
public class MyDefaultComponent implements DataBindingComponent {
public MyBindingAdapter mAdapter = new MyBindingAdapter();
public MyBindingAdapter getMyBindingAdapter() {
return mAdapter;
}
class MyBindingAdapter {
@BindingAdapter("android:text") public void setText(TextView textView, String text) {
/*省略*/
textView.setText(text);
}
}
}
復(fù)制代碼
設(shè)置默認(rèn)組件都是由DataBindingUtils設(shè)置, 但是方法也有所不同
static DataBindingComponent getDefaultComponent()
static void setDefaultComponent(DataBindingComponent bindingComponent)
復(fù)制代碼
以上這種設(shè)置必須在綁定視圖之前設(shè)置, 并且是默認(rèn)全局的, 只需要設(shè)置一次.
static <T extends ViewDataBinding> T setContentView(Activity activity,
int layoutId, DataBindingComponent bindingComponent)
復(fù)制代碼
類似于上面這種在綁定視圖的同時來設(shè)置組件需要每次綁定視圖都設(shè)置, 否則就會報錯.
或者你可以將@BindingAdapter注解的方法變?yōu)镾tatic修飾.
另外不僅僅是@BindingAdapter可以設(shè)置成組件, @InverseBindingAdapter同樣可以
注意
- 可以使用include不過不能作為root布局. merge不能使用
- 如果沒有自動生成DataBinding類可以先寫個variable(或者make module下)
推薦插件
關(guān)于DataBinding我推薦使用插件生成, 方便快捷很多;
DataBindingModelFormatter
快捷生成實現(xiàn)Observable
的數(shù)據(jù)模型
DataBindingSupport
自動生成DataBinding所需的XML格式