##寫在前面
最近冷靜了一個星期,學了大名鼎鼎的DataBinding。導師說這跟H5一樣,是未來Android發展的趨勢。看了一下,確實是Google官方跟著MVVM一起推出來的,所以就學了相關的知識,個別也運用到專案中。
不過講心裡話,不知道是不是平時的findViewById用習慣了,還是ButterKnife太好用了,一直覺得用這樣的方式去繫結資料有點渾身不得勁,先學著吧。
所以今天來分享一下學習DataBinding過程。不過不同的是,在這裡我不介紹它的使用規範或者方法,因為目前網上關於DataBinding的優秀文章很多很多,文末也會有相關連結。
只不過由於大部分文章都是針對Gradle1.5版本去寫的,有些方法和注意事項已經不適用了,而且文章幾乎只分開介紹了基本使用,並沒有介紹開發中的一些實際問題,我在學的過程中也踩了不少坑,所以本篇文章主要先介紹一下新版本的不同(坑),然後用一個小的實戰專案來具體展示如何使用DataBinding。
##站在前人的肩膀上,幫後人踩坑
###初始配置
第一次使用的時候需要注意,Gradle版本需要大於1.5。如果大於1.5,只需要在當前 Module 下的 build.gradle 檔案中新增如下程式碼即可:
android {
...
dataBinding {
enabled true
}
...
}
複製程式碼
注意,只需要在 android{ } 裡面加省略號之間的三行就行了,其他啥都不用配置。在這裡,我專案用的版本是 Gradle 2.1.3 。
###使用注意事項
1、 android:text 中只能寫" @{...}"的形式
在其他優秀DataBinding使用教程,我們學會了像下面這樣的使用規範:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}" />
複製程式碼
但是千萬不能這樣寫:
<TextView
...
android:text="年齡@{user.age}" />
<TextView
...
android:text="年齡+@{user.age}" />
複製程式碼
對比一下吧,沒有對比就沒有傷害:
當然如果你這樣寫的話:
<TextView
...
android:text="@{年齡user.age}" />
<TextView
...
android:text="@{年齡+user.age}" />
複製程式碼
壓根編譯都過不去,請珍惜生命,善待自己。
2、注意 android:text 中是 String 型別
假如上面的 user.age 是 int 型別的話,一定要記得轉成 String 型別,怎麼轉?當然是 String.valueOf() 了:
<TextView
...
android:text="@{String.valueOf(user.age)}" />
複製程式碼
3、引用資源Bug已經被解決
在之前所有的文章中,都有提到官方有個bug,說在引用資源時必須加上 int 才能通過編譯。
但是經過我的測試,不加也是可以編譯且正常執行的,可能這個bug已經被解決掉了:
<!--不同間距-->
<TextView
...
android:text="@{person.name}"
android:paddingLeft="@{person.isTrue ? @dimen/largeDP : @dimen/smallDP}"/>
<!--不同字號-->
<TextView
...
android:text="@{person.phone}"
android:textSize="@{large ? @dimen/largeSP : @dimen/smallSP}" />
複製程式碼
從下圖可以看到,第一個行間距不同,第二行字號不同:
4、include不能新增在非根結點的ViewGroup中?
同樣的,在之前幾乎所有的教程中,說 include 標籤不能非根結點的 ViewGroup 中,說程式會Crash掉,但是經過我的測試,程式也能正常編譯執行,估計也是一個Bug吧:
<!--根結點-->
<LinearLayout
...
android:orientation="vertical">
<include
layout="@layout/layout_text"
bind:user="@{user}" />
<include
layout="@layout/layout_button"
bind:btClick="@{btClick}" />
<!-- 非根節點 ViewGroup 仍然有效-->
<LinearLayout
...
android:orientation="vertical">
<include
layout="@layout/layout_text"
bind:user="@{user}" />
<include
layout="@layout/layout_button"
bind:btClick="@{btClick}" />
</LinearLayout>
</LinearLayout>
複製程式碼
如下圖,佈局是一樣的:
以上兩個Bug問題是僅在有限測試後得出的結論,如有錯誤請指正,謝謝!
5、使用資源或者屬性時,記得引包
什麼意思呢,比如下面根據 large 的值引用不同資源/屬性:
<!--顯示-->
<TextView
...
android:visibility="@{large ? View.INVISIBLE : View.VISIBLE}"/>
<!--顏色-->
<TextView
...
android:textColor="@{large ? Color.RED : Color.YELLOW}"/>
複製程式碼
這個時候,我們用到了 View 包和 Color 包,所以必須在 data 標籤中 import 這兩個包,才能顯示正常:
<import type="android.view.View" />
<import type="android.graphics.Color"/>
複製程式碼
從下圖可以看到第一行沒有顯示,第二行是紅色的:
6、自定義繫結類名稱注意包名
一般來說,編譯器會給我們自動生成一個繫結類,類名與佈局檔案的名稱一致。有些時候,我們需要自己自定義:
<import class="**.CustomBinding" />
複製程式碼
需要注意的是,這裡 CustomBinding 之前的 * 號部分必須是專案的包名,那包名需要一直寫到哪呢?
這個沒有具體規定,比如我專案的目錄是這樣的:
|--com
|--dataBinding
|--adapter
|--bean
|--pojo
|--ui
|--utils
複製程式碼
我可以寫 com.databinding.CustomBinding 也可以寫 com.databinding.ui.CustomBinding ,只要在專案包中即可。當然了,不能只寫個 com.CustomBinding ,這是個細節問題,大家注意一下就好。
7、XML檔案中不能包含< >符號
實際上這是個很有趣的問題,開始我還沒有發現,先看下面這段程式碼:
<!--資料層-->
<data>
<import type="android.databinding.ObservableMap" />
<variable
name="muser"
type="ObservableMap<String, Object>" />
</data>
複製程式碼
這裡的 variable 型別為一個 ObservableMap ,既然是 Map 當然就有 Key 和 Value ,這裡是 String 型別的 Key ,Object 型別的 Value ,但是注意這裡並不是你看錯了,就是這麼寫的。因為這裡不允許使用 "< >" 這樣的格式,否則會直接報錯。
8、IDE的各種問題
因為DataBinding推出不是很久,用的人不是很多,聽說在早前的AS版本中,IDE是沒有智慧提示的。但是現在的IDE已經對DataBinding有了很好的支援了,但是仍然還是有很多小的問題。
比如在XML檔案的UI層的根結點中,一些常用的屬性沒法提示,連 width 和 height 有時候都打不出來,但是如果真的手打出來的話,仍然是有效的,估計是IDE的問題。
同樣的,有時候在Activity中想要得到繫結類的時候,總是提示沒有這個類,但是繫結命名卻跟XML檔案命名一致的,這個時候我們只要把XML檔名稱隨便重新命名一下就行了,估計也是IDE沒有反應過來。
###常見問題
1、xx missing it
Error:Execution failed for task ':DataBindingDemo:compileDebugJavaWithJavac'.
> java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Identifiers must have user defined types from the XML file. Color is missing it
file:D:\Android\AndroidProject\Android5.0Demo\DataBindingDemo\src\main\res\layout\activity_resource.xml
loc:120:45 - 120:49
****\ data binding error ****
複製程式碼
這個問題很常見,不過跟我上面說的第五點是一致的,大部分都是因為沒有導包的原因,所以找不到這個 Color 。
解決辦法就是一句話,什麼Missing導什麼。
2、Could not find method XX
這也是個細節性問題了,比如下面這段程式碼(有省略):
<variable
name="btclick"
type="android.view.View.OnClickListener" />
...
<!--點選事件-->
<Button
...
android:onClick="btclick" />
複製程式碼
這裡就是給按鈕定義一個監聽,一般我們寫 android:onClick 的時候直接寫方法名就可以了,但是這裡如果我們直接寫的話,能編譯通過,但是一點選就會Crash,錯誤如下:
java.lang.IllegalStateException: Could not find method btclick(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton
提示說沒有找到方法,解決辦法就是 android:onClick="@{btclick}" ,沒什麼好解釋的,細心細心。
3、NullPoint怎麼辦
這個問題也很有趣,剛接觸DataBinding的時候,我也有相同的疑問,但是實際上,DataBinding不會出現NollPoint,至少在已經繫結的物件上不會出現。
為什麼呢?下面這是Data Binding(資料繫結)使用者指南給出的解釋:
“Data Binding程式碼生成時自動檢查是否為nulls來避免出現null pointer exceptions錯誤。例如,在表示式@{user.name}中,如果user是null,user.name會賦予它的預設值(null)。如果你引用user.age,age是int型別,那麼它的預設值是0。”
可以看到,DataBinding已經給我們處理好了,所以不用擔心這個問題。
4、編譯出錯,控制檯一大串錯誤資訊怎麼辦
在編譯過程中,如果有錯,控制檯會列印一大串問題,這個時候只需要看錯誤資訊的最後一條就行了。
而且一般來說,關鍵性錯誤資訊都是下面這樣的格式:
****/ data binding error ****
msg:...
file:...
****\ data binding error ****
複製程式碼
##舉個例子
好了,大概瞭解了DataBinding的使用方式,現在來用一個小小的例子來說具體如何在專案中使用它。
關於下面的例子很簡單,但是有幾點說明:
- 只說DataBinding相關部分;
- 特地選用了比較複雜的RecyclerView;
- 特地在item中新增圖片和文字,圖片來源為Url;
- 重點在於RecyclerView的動態繫結。
先來看一下item中的佈局吧,也是精簡了一部分:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="news"
type="com.databinding.bean.NewsBean" />
</data>
<android.support.v7.widget.CardView
...>
<ImageView
.../>
<TextView
...
android:text="@{news.title}"/>
<TextView
...
android:text="@{news.desc}"/>
<TextView
...
android:text="@{news.time}"/>
</android.support.v7.widget.CardView>
</layout>
複製程式碼
可以看到,這裡只有TextView使用了DataBinding,圖片並沒有設定,讓我們來看看在複雜的佈局中如何使用動態繫結。
因為是一個列表資料,item是複用的,所以沒辦法直接繫結,那麼就給ViewHolder繫結。
直接上原始碼吧,難度不大:
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.NewsViewHolder> {
private List<NewsBean> list;
private Context context;
public NewsAdapter(Context context, List<NewsBean> list) {
this.list = list;
this.context = context;
}
@Override
public NewsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_news, parent, false);
return new NewsViewHolder(itemView);
}
@Override
public void onBindViewHolder(NewsViewHolder holder, int position) {
//給ViewHolder繫結
holder.bind(list.get(position));
//載入圖片
Picasso.with(context).load(list.get(position).getPicUrl()).into(holder.itemPicImage);
}
@Override
public int getItemCount() {
return list.size();
}
public class NewsViewHolder extends RecyclerView.ViewHolder {
//繫結類
private ItemNewsBinding inBinding;
private ImageView itemPicImage;
public NewsViewHolder(View itemView) {
super(itemView);
//為每一個item設定
inBinding = DataBindingUtil.bind(itemView);
itemPicImage = (ImageView) itemView.findViewById(R.id.iv_item_pic);
}
/**
* 繫結方法
*
* @param news Bean物件
*/
public void bind(@NonNull NewsBean news) {
inBinding.setNews(news);
}
}
複製程式碼
如果你仔細看了上面的程式碼,你會發現其實只是多了三件事情:
- ViewHolder中建立繫結類;
- 利用繫結類設定相關內容;
- 繫結ViewHolder的時候繫結上相關內容。
就這麼多,來看看效果圖吧:
其例項子本身很簡單,但是想說的不是例子本身。從程式碼中我們可以發現,圖片的載入並沒有使用DataBinding,而是用Picasso直接載入。當然也可以使用DataBinding的靜態方法去直接展示,但是感覺有點怪怪的。而且如果這個item的佈局十分複雜,考慮的情況很多的話,這種情況顯然很麻煩很麻煩。何況這裡還沒有考慮點選事件。
所以總的來說就是,不熟悉就慎用慎用。
##總結
因為是剛接觸,對這種資料繫結的方式各種不適應,而且很多方法可能暫時不知道,就像上面複雜的佈局不知道有沒有更簡單的方法去處理。反正給我的感覺就是有點麻煩,也不準備繼續深入學習DataBinding了。
###優點
- 節省了給View設定Id的工作;
- 節省了findViewById的工作;
- 大部分情況都能很快捷的處理;
- 沒有空指標的問題;
- 還有很多優點待發掘。
###缺點
- ButterKnife好像更方便;
- 複雜的佈局處理起來很困難;
- IDE不夠完善。
- 還有很多缺點待發現。
###資源分享
Data Binding(資料繫結)使用者指南 - 田浩浩_DockOne
完全掌握Android DataBinding - 泡在網上的日子
Android資料繫結框架DataBinding,堪稱解決介面邏輯的黑科技 - 非著名程式設計師
###專案原始碼
個人部落格:www.iamxiarui.com 原文連結:http://www.iamxiarui.com/?p=805