一、DataBinding使用
1.使用環境
DataBinding是一個support library,所以它可以支持所有的android sdk,最低可以到android2.1(API7)。
使用DataBinding需要Android Gradle插件的支持,版本至少在1.5以上,需要的Android studio的版本在1.3以上。
在Android Studio上使用,需要在module級別的build.gradle上添加對DataBinding的支持:
android {
....
dataBinding {
enabled = true
}
}
如果是在library中使用,那么使用使用該library的module也需要在build.gradle添加。
2.xml布局文件數據綁定
DataBinding的layout files和普通的非DataBinding布局文件是有一些區別的,下面是一個基礎的使用了DataBinding的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<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}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
變量user作為被綁定的數據,在layout文件中是這樣描述和使用的:
<variable name="user" type="com.example.User"/>
layout中view的屬性值通過"@{}"這樣的語法表達方式和數據user實現綁定,本例中將TextView的text值設置為user對象的fisrtName了:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
3.定義數據綁定的Data對象
Data對象官方文檔中POJO類和Java Bean都可以,這里我建議使用如下Java Bean:
public class User {
private String firstName;
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
Note:但是定義一個如上的數據,并不能滿足刷新UI的要求,我們需要的Data 還得是一個Observable Data。
DataBinding中有三種不同的數據,object、field、collection。
Observable Objects
Observable是提供添加移除監聽的一個java接口,DataBinding基于此接口提供了一個基礎類BaseObserable,我們可以這樣使用它,通過Bindale注解綁定一個getter,當data屬性發生改變在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);
}
}
ObseravbleField
google為我們提供了一些Obserable類: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。
public static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
ObseravbleCollection
** ObservableArrayMap **
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在xml中使用:
<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);
xml使用:
<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"/>
4.綁定數據
Android studio會根據layout文件自動生成一個默認的Binding類,類名是根據layout文件名生成的,并有"Binding"后綴結束。例如:activity_main.xml生成的Binding類為ActivityMainBinding,可用如下方式使用Binding類:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
啟動程序,將會看到user的數據已經在ui中顯示了,或者我們也可以這樣實現:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
在ListView或者RecyclerView的adpater中item里使用DataBinding
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
5.事件處理
DataBinding允許我們在xml中view的一些事件屬性(如onClick等)中填寫DataBinding表達式,也可以通過綁定listener的方式去實現。歸納起來就是:
方法引用和監聽綁定,下面介紹著兩種方式:
方法引用
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<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:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
監聽綁定
public class Presenter {
public void onSaveClick(Task task){}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</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="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
6.imports
導入類:
<data>
<import type="android.view.View"/>
</data>
現在可以在xml中使用View類的靜態資源:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
也可以為導入類,重新定義一個別名:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
下面是個集合的例子:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
7.自定義Binding類類名
生成當前目錄下的ContactItemBinding
<data class="ContactItem">
...
</data>
也可以通過下面的方式指定生成類存放的目錄
<data class="com.example.ContactItem">
...
</data>
8.表達式
支持的運算符:
<ul>
<li>數學運算符: + - / * %</li>
<li>字符串拼接: +</li>
<li>邏輯運算符: && ||</li>
<li>二進制: & | ^</li>
<li>一元運算符: +</li>
<li>位運算符: >> >>> <<</li>
<li>比較: == > < >= <=</li>
<li>instanceof</li>
<li>()</li>
<li>數據類型: character, String, numeric, null</li>
<li>類型轉換(ClassCast)</li>
<li>方法回調(Method calls)</li>
<li>數據屬性</li>
<li>數組:[]</li>
<li>三元操作符:?</li>
</ul>
列如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
一些在java中常用而DataBinding xml中不支持的:
<ul>
<li>this
<li>super
<li>new
<li>泛型
</ul>
一個比較有意思的“??”操作符:
android:text="@{user.displayName ?? user.lastName}"
它等于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
9.Binding類的其他生成方式
前面,我們提到了一個獲取Binding類的方法,我們還可以這樣
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
或者:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
二、DataBinding高級使用
1.動態變量
有時候我們可能不知道Binding類的名稱,比如RecyclerView.Adapter中item布局可能有很多,并不會對應特定的Binding類,但是仍然需要通過** onBindViewHolder(VH, int)**去綁定數據,下面的列子是,所有的子布局都有一個"item"變量,通過ViewDataBinding基類去完成綁定:
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
Immediate Binding
當一個變量被綁定或者綁定的對象發生變化是,DataBinding會讓這些改變排隊去在下一幀刷險之前改變,有些時候binding效果必須立刻執行,這時候可以使用executePendingBindings()。
Background Thread
只要綁定數據不是一個collection,我們可以在非ui主線程去改變數據,不會有任何線程切換問題,DataBinding會自動處理。
2.Attribute Setters
當一個被綁定的數據的值發生改變時,Binding類會自動尋找該view上的綁定表達式上的方法去改變view,通過google數據綁定框架我們可以去自定義這些方法。
對于一個xml的attribute,data binding會去尋找setAttribute方法,xml屬性的命名空間是沒有關系的。比如TextView上的一個屬性android:text,會去尋找setText(String)。如果表達式返回的是int則會去尋找setText(int),所以必須確保xml中表達式返回正確的數據類型,必要時需要數據轉換。我們可以比較容易地為任何屬性創造出setter去使用dataBinding。比如support包下的DrawerLayout沒有任何屬性,但是確有很多setter,下面利用這些已有的setter中的一個:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
自定義setters
一些xml屬性需要自己去定義并實現邏輯,比如android:paddingLeft。但是setPadding(left,top,right,bottom)是存在的,那么我們可以同BindingAdapter注解去自定義個自己的setter:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Note:開發者自定義的BindingAdapter和android自帶的發生沖突時,data bingding會優先采用開發者自定義的。
多參數的BindingAdapter
@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);
}
BindingAdpater方法可以對屬性的舊值和新值進行處理
@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);
}
}
}
3.雙向綁定
在xml屬性上使用語法"{@=}",
使用該方法就是雙向綁定了
<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}"/>
</LinearLayout>
Note:需要注意的是,使用該語法必須要要反向綁定的方法,android原生view都是自帶的,所以使用原生控件無須擔心,但是自定義view的話需要我們通過InverseBindingAdapter注解類實現,下面是個例子
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String captureTextValue(TextView view, CharSequence originalValue) {
CharSequence newValue = view.getText();
CharSequence oldValue = value.get();
if (oldValue == null) {
value.set(newValue);
} else if (!contentEquals(newValue, oldValue)) {
value.set(newValue);
}
}
4.Converters
有時候我們想這樣寫xml屬性
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
但是xml屬性的setter是一個drawable,我們可以通過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"/>
三、關于DataBinding的一些個人看法
1.DataBinding使用心得
使用xml進行view布局
采用符合Java Bean規范的數據原型
規范的自定義View
禁止在BindingAdapter中的setter方法中改變數據或者做數據處理
不建議用BindingConversion處理數據轉換
不建議在xml布局中處理view事件
不建議在xml中使用復雜的表達式
2.DataBinding使用的一些思考
DataBinding的不足之處:
1.DataBinding在xml提供了豐富的操作符,但是由于Android studio天生的xml語法檢查的貧弱,xml布局中的表達式邏輯錯誤,不能準確定位,導致debug難度增加,事實上一些BindingAdapter的錯誤在build的時候也會被提示xml錯誤。
2.對自定義view的要求比較高,需要自定義綁定方法,如BindingAdapter等。
3.可能由于java 8移除apt,采用了新的API的緣故,所以即使Android Studio2.2已經開始支持java 8特性,但是需要開啟jack編譯鏈,DataBinding與之沖突,導致在代碼中不能使用lambda表達式等java 8特性。值得欣慰的是,這一問題將在Android Studio2.4中得到解決。
PS:數據綁定的應用軟件開發的一種趨勢,使用DataBinding的優點顯而易見,但是使用的時候我們也需要小心。