Data Binding Library
https://github.com/soul-ferry/DataBindingDemo
簡介:
Data Binding Library
是一個靈活壁熄、兼容的support library
,可以在Android2.1(API 7)以上使用。
使用DataBing,Gradle的Android Plugin需要在1.5.0-alpha1以上。
Build Environment
首先需要在Android SDK manager中下載DataBing的library。然后在build.gradle中添加dataBindingg節(jié)點,如下:
android {
....
dataBinding {
enabled = true
}
}
如果你的應用中有其他模塊或依賴的庫用到了Data Binding胰耗,也需要在buidl.gradle中配置。
Data Binding 的布局文件
第一個DataBinding表達式
使用DataBinding
后的布局文件與原來的布局文件差距很小,使用DataBinding
后的布局文件的根布局是layout
,并添加了data
節(jié)點檬果。布局格式如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- import and variable-->
</data>
<!-- 使用Data Binding前的根布局 -->
</layout>
eg.
<?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>
data
節(jié)點中的user
變量描述了可能會在布局中用到的屬性值。
DataBinding表達式寫在"@{}"
中唐断。在下面的例子中选脊,TextView的text被設置為user
的firstName
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data對象 (ViewModel)
假設你有一個User的POJO
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
這種對象的數據永不會改變。常用于應用中只讀一次永之后不改變的數據脸甘。
也可以使用JavaBeans:
public class User {
private final String firstName;
private final 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;
}
}
在DataBinding
中上面兩個User對象是一樣的恳啥。@{user.firstName}
會訪問第一個類的firstName字段第二個類的
getFirstName()
方法。如果存在firstName()
方法丹诀,也可能會訪問firstName()
方法钝的。
綁定數據
默認情況下翁垂,Binding類會根據布局文件的名字自動生成。如main_activity.xml
的布局文件會生成MainActivityBinding
硝桩。一個簡單的數據綁定如下:
@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);
}
Event Handling
Data Binding
提供了兩種處理事件的方法沮峡。
Method References(方法引用)
:你可以直接引用和listener
方法簽名一致的方法。當一個表達式被解析為Method References
亿柑,Data Binding
會把該方法包裝成一個listener
并設置到對應的View
邢疙。如果表達式被解析為null
,Data Binding
會設置成對應的listener
為null
望薄。Listener Bindings
:也可以使用lambda
,Data Binding
總是會創(chuàng)建設置到View
上的listener
,當事件被分發(fā)到該View
時lambda
表達式會被解析疟游。
Method References
與可以在android:onClick
屬性指定調用的Activity
方法類似,事件可以直接綁定到Handler
的方法上痕支。后者的優(yōu)勢在于實在編譯時處理颁虐,如果方法不存在或簽名不一致會得到一個編譯時錯誤。下面的例子展示了該用法:
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>
注意:
Handler
中方法必須與監(jiān)聽器中方法參數及返回值一樣卧须。如上面的例子中Handler
的方法為public void onClickFriend(View view)
另绩,android:onClick
需要的監(jiān)聽器為OnClickListener
,其方法為public void onClick(View view)
Listener Bindings
Listener Bindings
只要求Handler
中方法與監(jiān)聽器中的方法返回值一致即可。
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>
在上面的例子中問我們并沒有定義傳遞給 onClick(android.view.View)方法的view參數花嘶。Listener bindings
提供了兩種傳遞監(jiān)聽器參數的選擇:你可以忽略所有的參數笋籽。如果你更喜歡使用帶參數的,上面的例子也可以改為
android:onClick="@{(view) -> presenter.onSaveClick(task)}
或者也可以傳遞更多參數
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}
避免監(jiān)聽器混淆
Listener
表達式非常強大椭员,可以讓你寫出更易讀的代碼车海。另一方面,復雜的Listener
表達式會讓你的布局變得難以讀懂隘击、管理侍芝。Listener
表達式應該盡量簡單,只從UI傳遞有效數據到Callback的方法中埋同。所有的業(yè)務邏輯都應該該在Callback的方法中實現州叠。
存在一些特有的點擊事件,它們的點擊事件的屬性并不是android:onClick
凶赁。下面的表格列舉了對應的屬性:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
Layout Details(布局詳情)
Imports
可以在<data>
節(jié)點使用import
語法咧栗,與java相同java.lang.*
也是自動導入的。
<data>
<import type="android.view.View"/>
</data>
當有相同類名時哟冬,可以使用別名:
<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
Variables
Variables
用來描述布局文件中的屬性值楼熄。
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
在編譯期間會對variable
進行類型檢查,只有實現了Observable
接口或是一個observable collection
的variable
才會被觀察浩峡,即當該variable
的數據發(fā)生變化UI
會更新。
為了適配不同的屏幕你可能會有多個布局文件错敢,Data Binding
會合并這些布局文件中的variable
翰灾,所以這些布局文件中的variable
不能有混淆缕粹。
Data Binding
會為每個variable
自動生成對應的setter
和getter
方法。variable
在調用setter
前都會有一個默認值:引用類型->null
纸淮、int
->0
平斩、布爾值->false
。
Data Binding
會自動生成一個context
的variable
咽块。它來值跟布局的getContext()
绘面。可以被用戶的variable
覆蓋侈沪。
Includes
variable
在使用了<include/>
布局中的應用:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
name.xml
和contact.xml
布局文件都必須有user variable
不支持include
直接作為merge
的直接子元素揭璃,如下面的布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
表達式語言
共同的功能
下面的表達式和Java中的一樣
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- T*ernary operator ?:
eg.
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
消失的操作
在Java中可用,但在Databinding
中不可用的操作
- this
- super
- new
- Explicit generic invocation
Null Coalescing Operator(非空運算)
android:text="@{user.displayName ?? user.lastName}"
等于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Property Reference(屬性引用)
無論Data采用的是那種定義方法亭罪,POJO瘦馍、Java Beans或ObservableFields,在DataBinding
的語法中引用都用下面
的方式应役。
android:text="@{user.lastName}"
Avoiding NullPointerException(自動避免空指針異常)
Data Binding
生成的代碼會自動檢查null
值情组,避免空指針異常。
例如@{user.name}
,如果user
為空箩祥,user,name
會被賦值為默認值(null
)院崇。如果是int
類型的默認值為0。
Collections (集合)
常見的集合:arrays, lists, sparse lists, and maps可以通過[]
訪問袍祖。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
String Literals (字符串)
如果需要在@{}
表達式內使用""
,布局的屬性值需要使用''
android:text='@{map["firstName"]}'
或者使用下面的方式
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
Resources (資源文件)
可以使用通常的語法在表達式中訪問資源文件:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化的字符串和plurals
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
一些資源需要使用指定的引用方式
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
Data Objects (數據對象)
任何一個POJO都可以用于Data Binding
亚脆,但是修改了一個并POJO不會更新UI。Data Binding
的real power是讓你的數據對象(Data Objects
)發(fā)生數據改變時能夠發(fā)出通知盲泛。這里有3種數據改變通知機制:Observable objects
, observable fields
, observable collections
濒持。
當一個實現了通知機制的數據對象的數據發(fā)生改變并且綁定到了UI,UI會自動更新寺滚。
Observable Objects (Observable對象)
Observable
interface有一個添加和移除listeners的機制柑营,但是發(fā)送通知取決于開發(fā)者。為了方便開發(fā),Data Binding
提供了一個BaseObservable
類村视,BaseObservable
實現了listener注冊機制官套。實現類要負責在數據改變時發(fā)出通知,這需要通過在getter
方法加上@Bindable
注解并且在setter
方法中發(fā)出通知蚁孔。
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
類中生成一個字段奶赔,BR
與R
類似。
ObservableFields
Data Binding
還提供了另外更細粒度杠氢、更方便的創(chuàng)建Observable
類的方式:ObservableFields
站刑,ObservableBoolean
, ObservableByte
, ObservableChar
, ObservableShort
, ObservableInt
, ObservableLong
, ObservableFloat
, ObservableDouble
, and ObservableParcelable
。
eg.
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
如何訪問:
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
一些App使用集合保存數據鼻百。Observable Collections
允許使用通過鍵
來訪問數據绞旅。
ObservableArrayMap
用于鍵
是引用類型
摆尝,如String
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局文件中的使用
<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
鍵
是int
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局文件中的使用
<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"/>
Generated Binding(生成Binding)
上文已經說過生成的Binding
類名由布局文件名決定。
Creating(創(chuàng)建)
Binding
類應該盡可能早地創(chuàng)建,綁定layout的方式有多種因悲,都是通過調用Binding
類的靜態(tài)方法堕汞。如:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
Views With IDs(帶id的View)
Data Binding
會為布局文件中每個帶ID的View生成一個public final
的字段。這可以讓替代findViewById
以更快的方式訪問View:
<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}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>
生成的Binding類中會有:
public final TextView firstName;
public final TextView lastName;
ViewStubs
布局文件中的ViewStub
經過binding之后會轉換成ViewStubProxy
晃琳。
簡單用代碼說明一下讯检,xml 文件與之前的代碼一樣,根節(jié)點改為 layout卫旱,在 LinearLayout 中添加一個 ViewStub人灼,添加 ID。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/view_stub_test"
android:layout="@layout/stub"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
</layout>
在 Java 代碼中獲取 binding 實例誊涯,為 ViewStubProy 注冊 ViewStub.OnInflateListener 事件:
binding.viewStubTest.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
StubBinding stubBinding = DataBindingUtil.bind(inflated);
ViewStubViewModel model = new ViewStubViewModel();
model.setName("boco");
stubBinding.setStubModel(model);
}
});
binding.viewStubTest.getViewStub().inflate();
Advanced Binding(高級綁定)
Dynamic Variables(動態(tài)設置Variables)
有時候挡毅,我們并不知道具體的Binding類型,如在RecyclerView.Adapter
中可能會有多個layout
,我們必須在onBindViewHolder(VH, int)
進行賦值暴构。
public static class BindingHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public BindingHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.getContext()),
R.layout.list_item,
viewGroup,
false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
Immediate Binding(立即綁定)
當一個variable或者observable發(fā)生了改變跪呈,數據綁定會在下一幀之前發(fā)生但不是立刻發(fā)生,如果你希望數據綁定立刻發(fā)生需要使用executePendingBindings()
方法,eg.
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
Background Thread
你可以在后臺線程中改變不是集合的數據取逾,Data binding will localize each variable / field while evaluating to avoid any concurrency issues.
Attribute Setters (屬性賦值)
自動賦值
有了 Data Binding耗绿,即使屬性沒有在 declare-styleable
中定義,我們也可以通過 xml 進行賦值操作砾隅。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:drawerListener="@{fragment.drawerListener}"/>
在上面的例子中app:drawerListener="@{fragment.drawerListener}
會調用DrawerLayout的setDrawerListener(DrawerListener listener)
方法具體的方法參數類型和@{}
中的參數類型一致误阻。
重命名
<Button
android:id="@+id/email_sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:myText="@{login.name}"
android:onClick="@{loginHandler.onClick}"
/>
在上面的xml對應的ViewModel中加入如下注解,app:myText="@{login.name}
便會調用Button的setText方法晴埂。
@BindingMethods({
@BindingMethod(type = android.widget.Button.class,
attribute = "myText",
method = "setText"),
})
自定義
ViewModel中的方法
@BindingAdapter({"app:myText"})
public static void showText(Button button,String newV,String oldV){
button.setText(newV);
Log.w(TAG,"new="+newV+"--oldV="+oldV);
}
or
@BindingAdapter({"app:myText"})
public static void showText(Button button,String newV){
button.setText(newV);
Log.w(TAG,"new="+newV);
}
對應得xml代碼:
<Button
android:id="@+id/email_sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:myText="@{login.name}"
android:onClick="@{loginHandler.onClick}"
/>
轉換器 (Converters)
在 xml 中為屬性賦值時究反,如果變量的類型與屬性不一致,通過 DataBinding 可以進行轉換儒洛。
例如精耐,下面代碼中如果要為屬性 android:background 賦值一個 int 型的 color 變量:
<View
android:background="@{isError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
只需要定義一個標記了 @BindingConversion 的靜態(tài)方法即可(方法的定義位置可以隨意):
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}