前言
后臺(tái)讀者留言:能否寫一篇視圖綁定ViewBinding相關(guān)的內(nèi)容?
首先感謝這位讀者的提議,讓我抽出時(shí)間細(xì)看視圖綁定的內(nèi)容烙常,也打算在項(xiàng)目中使用該功能。當(dāng)然鹤盒,還有其他讀者提議的內(nèi)容我已記錄蚕脏,后期有時(shí)間也會(huì)陸續(xù)更新。話不多說(shuō)侦锯,我們開(kāi)始學(xué)習(xí)吧驼鞭!
概述
在我們的開(kāi)發(fā)過(guò)程中,需要獲取XML布局文件中的ViewId尺碰,以便其賦值顯示挣棕,我們習(xí)慣使用findViewById進(jìn)行操作译隘,可這樣會(huì)導(dǎo)致很多的模版代碼出現(xiàn)。直到Android大神 Jake Wharton開(kāi)源了Butter Knife框架洛心,通過(guò)Bind方式綁定獲取ViewId固耘。近兩年谷歌對(duì)Kotlin的支持,我們開(kāi)始使用 Android Kotlin extensions词身。在文件中導(dǎo)入布局文件直接引用viewId厅目。無(wú)需做其他額外操作,最為方便法严。
目前损敷,谷歌在 Android Studio 3.6 Canary 11 及更高版本中加入了新的視圖綁定方式ViewBinding。
注意:要使用ViewBinding功能渐夸,AndroidStudio至少要升級(jí)到3.6嗤锉。
分析
本文主要從以下方面對(duì)ViewBinding進(jìn)行分析:
- 使用能解決什么問(wèn)題;
- 使用流程墓塌;
- 與之前方法的比較瘟忱;
- 原理;
1.使用能解決什么問(wèn)題
顧名思義ViewBinding的意思就是如何將view與代碼綁定在一起苫幢。所以其主要解決如何安全優(yōu)雅地從代碼中引用到XML layout文件中的view控件的問(wèn)題访诱。直到目前為止,Android構(gòu)建用戶界面的主流方式仍然是使用XML格式的layout文件韩肝。
2.使用流程
- 在要使用ViewBinding的 module 的gradle文件中開(kāi)啟ViewBinding
android {
……………
viewBinding {
enabled = true
}
……………
}
- 如果在使用的過(guò)程中開(kāi)發(fā)者不想為某個(gè)布局文件生成binding類触菜,則可以使用如下屬性添加到布局的根視圖中即可:
<androidx.constraintlayout.widget.ConstraintLayout
…………
tools:viewBindingIgnore="true" >
…………
</androidx.constraintlayout.widget.ConstraintLayout>
- 編譯此module獲得XML布局文件對(duì)應(yīng)的綁定類
在gradle文件中開(kāi)啟ViewBinding功能后,編譯器就會(huì)為此模塊下的每個(gè)布局文件都產(chǎn)生一個(gè)對(duì)應(yīng)的綁定類。
假設(shè)我們有如下XML布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
tools:viewBindingIgnore="true" >
<ImageView
android:id="@+id/img_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
這個(gè)類名稱的命名規(guī)則為:XML布局文件名去掉下劃線哀峻,下劃線首字母大寫涡相,最后加上Binding。例如我有一個(gè)布局文件activity_main.xml,那對(duì)應(yīng)生成的類文件為ActivityMainBinding.java剩蟀。
生成的類文件位于Module路徑:
build\generated\data_binding_base_class_source_out\debug\out\包名\databinding下催蝗。
如下圖所示:
- 使用此生成類引用XML布局文件中的控件
調(diào)用生成類ActivityDescriptionBinding的inflate()方法獲得類實(shí)例對(duì)象,通過(guò)getRoot()方法可以獲得layout文件的最外層View,此例中是一個(gè)ConstraintLayout. 通過(guò)Activity的 setContentView()方法可以為Activity設(shè)置內(nèi)容育特。layout文件中只要是有id的view, 在這個(gè)生成類中都會(huì)對(duì)應(yīng)的生成一個(gè) public final 的屬性丙号,例如:
<TextView
android:id="@+id/tv_content"
...
/>
對(duì)應(yīng)的生成字段為:
@NonNull
public final TextView tvContent;
那就可以直接使用對(duì)象實(shí)例訪問(wèn)了,如下代碼所示:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding activityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設(shè)置布局文件
activityMainBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
setContentView(activityMainBinding.getRoot());
//設(shè)置文本
activityMainBinding.tvContent.setText("要么停止成長(zhǎng)缰冤,要么不斷向前");
//設(shè)置圖片
activityMainBinding.imgShow.setImageResource(R.drawable.img);
}
}
3.與之前方法的比較
目前Android開(kāi)發(fā)中完成View映射的方法主要有 findViewById犬缨、 ButterKnife, 如果使用kotlin的話還可以使用Kotlin Android Extensions。
這些方式的各方面對(duì)比如下:
ViewBinding對(duì)比以上方法有如下幾點(diǎn)優(yōu)勢(shì):
-
Type safety:findViewById, ButterKnife 均存在類型轉(zhuǎn)換問(wèn)題棉浸,例如不小心將一個(gè)TextView錯(cuò)誤的賦值給一個(gè)Button變量怀薛,都會(huì)報(bào)錯(cuò),這一錯(cuò)誤很容易出現(xiàn)迷郑,關(guān)鍵在錯(cuò)誤還出現(xiàn)在運(yùn)行時(shí)乾戏,而不是編譯時(shí)迂苛!
而ViewBinding中三热,產(chǎn)生的binding類中的屬性是依據(jù)XML layout文件生成的鼓择,所以類型不會(huì)錯(cuò),生成的時(shí)候已經(jīng)處理好了就漾。
Null safety: findViewById, ButterKnife與Kotlin Android Extensions 均存在Null不安全問(wèn)題呐能。這個(gè)什么意思呢?就是在我們?cè)L問(wèn)那個(gè)View的時(shí)候它不存在抑堡。為什么會(huì)出現(xiàn)這種情況呢摆出?例如不小心使用了錯(cuò)誤的Id,或者訪問(wèn)的時(shí)候那個(gè)view還不存在。
使用了錯(cuò)誤Id這個(gè)估計(jì)大家都有此類經(jīng)歷首妖,但是訪問(wèn)時(shí)候那個(gè)view不存在怎么理解呢偎漫?例如我們?cè)谑謾C(jī)橫屏和豎屏的時(shí)候分別使用一套XML layout文件,假設(shè)橫屏中包含了一個(gè)豎屏中沒(méi)有的view,那么在屏幕從橫屏旋轉(zhuǎn)到豎屏的時(shí)候有缆,NullPointer問(wèn)題就出現(xiàn)了象踊。
而ViewBinding中, 產(chǎn)生的binding類中的屬性是依據(jù)XML layout文件生成的棚壁,所以Id不會(huì)錯(cuò)杯矩。而且其將僅存在某一個(gè)配置下的layout文件的那些view對(duì)應(yīng)的字段標(biāo)記為@Nullable ,例如本例中的:
@NonNull
public final TextView tvContent;
而且,生成類中還會(huì)很貼心的給你加上詳細(xì)的注釋袖外。這一切都是為了提醒程序員史隆,注意對(duì)這個(gè)view特別處理,它在某些情況下為Null曼验。
- 簡(jiǎn)潔優(yōu)雅: 將綁定view的模板代碼自動(dòng)生成到了其他類中泌射,使controlor類(Activity,F(xiàn)ragment)更加清晰了鬓照。
4.原理
通過(guò)上面分析熔酷,估計(jì)你對(duì)其原理也猜的的八九不離十了。就是Google在那個(gè)用來(lái)編譯的gradle插件中增加了新功能颖杏,當(dāng)某個(gè)module開(kāi)啟ViewBinding功能后纯陨,編譯的時(shí)候就去掃描此模塊下的layout文件,生成對(duì)應(yīng)的binding類留储。那些你所熟悉的findViewById操作都是在這個(gè)自動(dòng)生成的類里面呢翼抠,如下所示:
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final ImageView imgShow;
@NonNull
public final TextView tvContent;
private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull ImageView imgShow,
@NonNull TextView tvContent) {
this.rootView = rootView;
this.imgShow = imgShow;
this.tvContent = tvContent;
}
@Override
@NonNull
public ConstraintLayout getRoot() {
return rootView;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
String missingId;
missingId: {
ImageView imgShow = rootView.findViewById(R.id.img_show);
if (imgShow == null) {
missingId = "imgShow";
break missingId;
}
TextView tvContent = rootView.findViewById(R.id.tv_content);
if (tvContent == null) {
missingId = "tvContent";
break missingId;
}
return new ActivityMainBinding((ConstraintLayout) rootView, imgShow, tvContent);
}
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
其中核心代碼是bind(@NonNull View rootView)方法,除此之外還有兩個(gè)inflate()重載方法获讳,一般情況下我們使用這兩個(gè)方法獲得binding類的實(shí)例阴颖,這些方法都是public static的,通過(guò)bind(@NonNull View rootView)這個(gè)方法應(yīng)該可以實(shí)現(xiàn)延遲綁定丐膝,但是其使用場(chǎng)景應(yīng)該很少量愧。
總結(jié)
目前ViewBinding的功能還不夠完善钾菊,比如XML中使用了 inClude 標(biāo)簽時(shí)無(wú)法對(duì)view進(jìn)行引用。但總體來(lái)說(shuō)已經(jīng)很不錯(cuò)了偎肃。相比較于 findViewById 和 Butter Knife兩種方式還是方便很多的煞烫。而且 ViewBinding 在使用的過(guò)程中不存在類型轉(zhuǎn)換以及空指針異常的問(wèn)題。因?yàn)樵诮壎愔幸呀?jīng)全部定義好了累颂,開(kāi)發(fā)者直接使用就可以滞详。