轉自:https://github.com/LyndonChin/MasteringAndroidDataBindinghtml
官方雖然已經給出了教程 - Data Binding Guide (中文版 - Data Binding(數據綁定)用戶指南) ,可是實踐以後發現槽點實在太多,因而就有了這個教程,針對每一個知識點給出更詳實的例子同時也總結了遇到的一些坑,但願對你有所幫助:)java
Data Binding 解決了 Android UI 編程的一個痛點,官方原生支持 MVVM 模型可讓咱們在不改變既有代碼框架的前提下,很是容易地使用這些新特性。android
Data Binding 框架若是可以推廣開來,也許 RoboGuice、ButterKnife 這樣的依賴注入框架會慢慢失去市場,由於在 Java 代碼中直接使用 View
變量的狀況會愈來愈少。git
新建一個 Project,確保 Android 的 Gradle 插件版本不低於 1.5.0-alpha1:github
classpath 'com.android.tools.build:gradle:1.5.0'
而後修改對應模塊(Module)的 build.gradle:編程
dataBinding {
enabled true
}
工程建立完成後,咱們經過一個最簡單的例子來講明 Data Binding 的基本用法。api
使用 Data Binding 以後,xml 的佈局文件就再也不用於單純地展現 UI 元素,還須要定義 UI 元素用到的變量。因此,它的根節點再也不是一個 ViewGroup
,而是變成了 layout
,而且新增了一個節點 data
。app
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> </data> <!--原先的根節點(Root Element)--> <LinearLayout> .... </LinearLayout> </layout>
要實現 MVVM 的 ViewModel
就須要把數據(Model)與 UI(View) 進行綁定,data
節點的做用就像一個橋樑,搭建了 View 和 Model 之間的通路。框架
咱們先在 xml 佈局文件的 data
節點中聲明一個 variable
,這個變量會爲 UI 元素提供數據(例如 TextView
的 android:text
),而後在 Java 代碼中把『後臺』數據與這個 variable
進行綁定。dom
下面咱們使用 Data Binding 建立一個展現用戶信息的表格。
添加一個 POJO 類 - User
,很是簡單,兩個屬性以及他們的 getter 和 setter。
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 firstName; } public String getLastName() { return lastName; } }
稍後,咱們會新建一個 User
類型的變量,而後把它跟佈局文件中聲明的變量進行綁定。
回到佈局文件,在 data
節點中聲明一個 User
類型的變量 user
。
<data>
<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" /> </data>
其中 type
屬性就是咱們在 Java 文件中定義的 User
類。
固然,data
節點也支持 import
,因此上面的代碼能夠換一種形式來寫。
<data>
<import type="com.liangfeizc.databindingsamples.basic.User" /> <variable name="user" type="User" /> </data>
而後咱們剛纔在 build.gradle 中添加的那個插件 - com.android.databinding
會根據 xml 文件的名稱 Generate 一個繼承自 ViewDataBinding
的類。 固然,IDE 中看不到這個文件,須要手動去 build 目錄下找。
例如,這裏 xml 的文件名叫 activity_basic.xml
,那麼生成的類就是 ActivityBasicBinding
。
注意
java.lang.*
包中的類會被自動導入,能夠直接使用,例如要定義一個 String
類型的變量:
<variable name="firstName" type="String" />
修改 BasicActivity
的 onCreate
方法,用 DatabindingUtil.setContentView()
來替換掉 setContentView()
,而後建立一個 user
對象,經過 binding.setUser(user)
與 variable
進行綁定。
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityBasicBinding binding = DataBindingUtil.setContentView( this, R.layout.activity_basic); User user = new User("fei", "Liang"); binding.setUser(user); }
除了使用框架自動生成的 ActivityBasicBinding
,咱們也能夠經過以下方式自定義類名。
<data class="com.example.CustomBinding"> </data>
注意
ActivityBasicBinding
類是自動生成的,全部的 set
方法也是根據 variable
名稱生成的。例如,咱們定義了兩個變量。
<data>
<variable name="firstName" type="String" /> <variable name="lastName" type="String" /> </data>
那麼就會生成對應的兩個 set 方法。
setFirstName(String firstName);
setLastName(String lastName);
數據與 Variable 綁定以後,xml 的 UI 元素就能夠直接使用了。
<TextView
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" />
至此,一個簡單的數據綁定就完成了,可參考完整代碼
首先定義一個靜態方法
public class MyStringUtils { public static String capitalize(final String word) { if (word.length() > 1) { return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1); } return word; } }
而後在 xml 的 data
節點中導入:
<import type="com.liangfeizc.databindingsamples.utils.MyStringUtils" />
使用方法與 Java 語法同樣:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{MyStringUtils.capitalize(user.firstName)}" />
若是咱們在 data
節點了導入了兩個同名的類怎麼辦?
<import type="com.example.home.data.User" /> <import type="com.examle.detail.data.User" /> <variable name="user" type="User" />
這樣一來出現了兩個 User
類,那 user
變量要用哪個呢?不用擔憂,import
還有一個 alias
屬性。
<import type="com.example.home.data.User" /> <import type="com.examle.detail.data.User" alias="DetailUser" /> <variable name="user" type="DetailUser" />
android:text="@{user.displayName ?? user.lastName}"
就等價於
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
經過 @{}
能夠直接把 Java 中定義的屬性值賦值給 xml 屬性。
<TextView
android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
這個例子,官方教程有錯誤,能夠參考Android Data Binder 的一個bug,完整代碼在此
<TextView
android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}" android:background="@android:color/black" android:textColor="@android:color/white" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" />
原本這一節的標題應該叫雙向綁定,可是很遺憾,如今的 Data Binding 暫時支持單向綁定,尚未達到 Angular.js 的威力。
要實現 Observable Binding,首先得有一個 implement
了接口 android.databinding.Observable
的類,爲了方便,Android 原生提供了已經封裝好的一個類 - BaseObservable
,而且實現了監聽器的註冊機制。
咱們能夠直接繼承 BaseObservable
。
public class ObservableUser extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return firstName; } @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); } }
BR
是編譯階段生成的一個類,功能與 R.java
相似,用 @Bindable
標記過 getter
方法會在 BR
中生成一個 entry。
經過代碼能夠看出,當數據發生變化時仍是須要手動發出通知。 經過調用 notifyPropertyChanged(BR.firstName)
能夠通知系統 BR.firstName
這個 entry
的數據已經發生變化,須要更新 UI。
除此以外,還有一種更細粒度的綁定方式,能夠具體到成員變量,這種方式無需繼承 BaseObservable
,一個簡單的 POJO 就能夠實現。
public class PlainUser { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); }
系統爲咱們提供了全部的 primitive type 所對應的 Observable類,例如 ObservableInt
、ObservableFloat
、ObservableBoolean
等等,還有一個 ObservableField
對應着 reference type。
剩下的數據綁定與前面介紹的方式同樣,具體可參考ObservableActivity。
Data Binding 有效下降了代碼的冗餘性,甚至徹底沒有必要再去獲取一個 View 實例,可是狀況不是絕對的,萬一咱們真的就須要了呢?不用擔憂,只要給 View 定義一個 ID,Data Binding 就會爲咱們生成一個對應的 final
變量。
<TextView
android:id="@+id/firstName" android:layout_width="wrap_content" android:layout_height="wrap_content" />
上面代碼中定義了一個 ID 爲 firstName* 的 TextView
,那麼它對應的變量就是
public final TextView firstName;
具體代碼可參考 ViewWithIDsActivity.java
xml 中的 ViewStub
通過 binding 以後會轉換成 ViewStubProxy
, 具體代碼可參考 ViewStubActivity.java
簡單用代碼說明一下,xml 文件與以前的代碼同樣,根節點改成 layout
,在 LinearLayout
中添加一個 ViewStub
,添加 ID。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout ...> <ViewStub android:id="@+id/view_stub" android:layout="@layout/view_stub" ... /> </LinearLayout> </layout>
在 Java 代碼中獲取 binding
實例,爲 ViewStubProy
註冊 ViewStub.OnInflateListener
事件:
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub); binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { ViewStubBinding binding = DataBindingUtil.bind(inflated); User user = new User("fee", "lang"); binding.setUser(user); } });
完整代碼能夠參考 dynamic
以 RecyclerView
爲例,Adapter
的 DataBinding 須要動態生成,所以咱們能夠在 onCreateViewHolder
的時候建立這個 DataBinding,而後在 onBindViewHolder
中獲取這個 DataBinding。
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(); }
注意此處 DataBindingUtil
的用法:
ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false);
還有另一種比較簡潔的方式,直接在構造 Holder 時把 View
與自動生成的 XXXBinding
進行綁定。
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> { private static final int USER_COUNT = 10; @NonNull private List<User> mUsers; public UserAdapter() { mUsers = new ArrayList<>(10); for (int i = 0; i < USER_COUNT; i ++) { User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName()); mUsers.add(user); } } public static class UserHolder extends RecyclerView.ViewHolder { private UserItemBinding mBinding; public UserHolder(View itemView) { super(itemView); mBinding = DataBindingUtil.bind(itemView); } public void bind(@NonNull User user) { mBinding.setUser(user); } } @Override public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View itemView = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.user_item, viewGroup, false); return new UserHolder(itemView); } @Override public void onBindViewHolder(UserHolder holder, int position) { holder.bind(mUsers.get(position)); } @Override public int getItemCount() { return mUsers.size(); } }
有了 Data Binding,即便屬性沒有在 declare-styleable
中定義,咱們也能夠經過 xml 進行賦值操做。 爲了演示這個功能,我自定義了一個 View - NameCard,屬性資源 R.styleable.NameCard 中只定義了一個 age
屬性,其中 firstName
和 lastName
只有對應的兩個 setter
方法。
只要有 setter
方法就能夠像下面代碼同樣賦值:
<com.liangfeizc.databindingsamples.attributesetters.UserView
android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="@dimen/largePadding" app:onClickListener="@{activity.clickListener}" app:firstName="@{@string/firstName}" app:lastName="@{@string/lastName}" app:age="27" />
onClickListener
也是一樣道理,只不過咱們是在 Activity
中定義了一個 Listener
。
很是重要
使用 Converter 必定要保證它不會影響到其餘的屬性,例如這個
@BindingConversion
- convertColorToString 就會影響到android:visibility, 由於他們都是都符合從 int 到 int 的轉換。
在 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
的靜態方法便可(方法的定義位置能夠隨意):
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
具體代碼可參考 ConversionsActivity.java。