勘誤:原文中使用modelview一詞,但實際上mvvm是model-view-viewmodel,故應為viewmodel。
在之前的文章中,我們使用data binding與布局中的<code>textview</code>配合搭建了簡單的app,并說到接下來将會加入圖檔的加載。這會比綁定文字更加麻煩,因為我們從twitter伺服器上拿下來的是url而不是直接的圖檔。現在是時候介紹在mvvm模式中model和viewmodel的差別了。在這個例子中,model擷取的是url,但是view需要的是一個bitmap,那麼model就需要轉換成viewmodel來适應view的需求。要将他們進行如此細緻的區分,是因為從url到bitmap是一個比較并不簡單的過程,需要根據url網絡上去擷取這個圖檔的具體内容。這部分的邏輯已經被我們将要使用的第三方圖像加載庫glide封裝了,不過我們還需要添加一些轉換邏輯。
在這了使用data binding + recyclerview還有一個原因。加載一幅網絡圖檔的代價是很大的,如果你将一個list裡面所有的圖檔都進行加載,而不論它是否要顯示到螢幕上,這是非常浪費性能的。是以應該隻加載要顯示到螢幕上的圖檔,資料綁定也應該隻在這種情況下進行。
我在上一篇中說了,我将使用glide來進行圖像加載。通過glide,你可以使用以下這行簡單的代碼來實作圖像的下載下傳、轉碼、加載過程:
1
2
glide.with(context).load(url).into(imageview);
它會自動異步下載下傳圖檔,并将它顯示到<code>imageview</code>上。glide還能夠幫你進行圖檔緩存等操作,不過我們在此處僅使用它最簡單的圖檔加載功能。
現在看起來我們可以将data binding和imageview結合起來了,這是一個很自然的想法,你可能會這麼做:
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="status"
type="com.stylingandroid.databinding.data.status"/>
name="glide"
type="com.bumptech.glide.glide"/>
</data>
<relativelayout
android:id="@+id/status_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
注意:這個裡的databinding将不起作用
-->
<imageview
android:id="@+id/status_avatar"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignparentleft="true"
android:layout_alignparentstart="true"
android:layout_alignparenttop="true"
android:layout_margin="8dip"
android:contentdescription="@null"
app:imageurl="@{glide.load(status.imageurl).into(this)}"/>
.
</relativelayout>
</layout>
這裡實際上有一個問題,我們可以檢視一下data binding的表達式文檔:
a few operations are missing from the expression syntax that you can use in java.
this
super
new
explicit generic invocation
你會發現:
在data binding語句中,<code>this</code>符号是不起作用的!
雖然<code>this</code>沒法使用了,但是我們可以用自定義setter來替代它。當你希望為一個view定義特殊的setter時可以這麼幹。你可以在自定義setter裡面利用glide将擷取的url轉化成bitmap顯示到imageview中。
這是一個自定義setter的例子:
publicfinalclassdatabinder{
privatedatabinder(){
//no-op
}
@bindingadapter("imageurl")
publicstaticvoidsetimageurl(imageviewimageview,stringurl){
contextcontext=imageview.getcontext();
glide.with(context).load(url).into(imageview);
}
以上是一個很簡單的工具類,它裡面有個函數名為<code>setimageview()</code>,以<code>imageview</code>和<code>string</code>作為輸入參數。不過你應該也注意到了,在這個函數上有一個注解:<code>@bindingadapter("imageurl")</code>,這個注解的作用是向data
binding庫聲明了一個名為imageurl的自定義setter。我們不需要再做其他任何配置,這個注解會在編譯時就幫我們打理好了一切。在加入這條注解之後,我們就可以在xml中使用它:
app:imageurl="@{status.imageurl}"/>
這個data binding的自定義屬性<code>imageurl是</code>在app命名空間下的(也就是res-auto)。它僅僅需要一個參數——url字元串,它所處的<code>imageview</code>會自動加到自定義setter中。在中也不需要再做其他改動,甚至也不需要加入glide執行個體變量。
這樣就完成了我們的圖像綁定和加載過程,如下圖所示:

雖然解釋了很多,實際上寫的代碼卻很少——幾行自定義setter,再在layout檔案中添加兩行代碼,就足夠了!在下一篇文章中我們再看看怎麼将data binding用在更加有意思的地方。
從我寫這篇文章開始到現在已經有很多人利用自定義setter做了很多有意思的事情。在droidcon nyc會議上,roman nurik向我展示了與本文類似的代碼。我的代碼靈感來源于官方的data binding手冊,特别是使用picasso結合自定義setter進行圖像加載的那部分。看起來使用glide比picasso更容易去了解和使用自定義setter,我相信除了我和roman還有很多人會這麼想,并且都會創作出類似的代碼。
lisa wray利用自定義setter做了一件很有意思的事情:她利用data binding為textview綁定了字型屬性!她的例子完美地展示了自定義setter可以實作多麼靈活的功能!