精通 Android Data Binding

轉自: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,而且新增了一個節點 dataapp

<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 類型的變量,而後把它跟佈局文件中聲明的變量進行綁定。

定義 Variable

回到佈局文件,在 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" />

綁定 Variable

修改 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

數據與 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" />

Null Coalescing 運算符

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" />

Observable Binding

原本這一節的標題應該叫雙向綁定,可是很遺憾,如今的 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類,例如 ObservableIntObservableFloatObservableBoolean 等等,還有一個 ObservableField 對應着 reference type。

剩下的數據綁定與前面介紹的方式同樣,具體可參考ObservableActivity

帶 ID 的 View

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

ViewStubs

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 Variables

完整代碼能夠參考 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(); } }

Attribute setters

有了 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

轉換器 (Converters)

很是重要

使用 Converter 必定要保證它不會影響到其餘的屬性,例如這個 @BindingConversionconvertColorToString 就會影響到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

相關文章
相關標籤/搜索