ViewBinding 使用簡介

1.背景

在日常app界面開發(fā)過程中,經(jīng)常會遇到如下場景:

  • 場景1:升級到較新版本Android Studio后,在使用R.id.xxxx時會出現(xiàn)警告提示:

    Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them as annotation attributes
    
  • 場景2:在日常UI界面開發(fā)過程中路幸,每個界面的View的id命名是一個相對頭疼的問題铡买。為了較好的區(qū)分每個id镣隶,通常id的命名會很長魂那,導(dǎo)致id寫起來啰嗦隙笆,也缺乏美觀锌蓄。不同的開發(fā)人員有各種喜好的命名風(fēng)格,導(dǎo)致命名上相對混亂撑柔。例如類似這種:

    R.id.app_item_game_comment_detail_bottom_user_name
    
  • 場景3:視圖變量聲明后瘸爽,使用過程中經(jīng)常出現(xiàn)空指針異常。當(dāng)前界面布局文件中沒有該id铅忿,View聲明中確使用了這個id剪决,導(dǎo)致空指針,程序崩潰退出檀训。

  • 場景4:butterknife 已經(jīng)停止維護(hù)了柑潦,不再升級更新。不想繼續(xù)使用 butterknife峻凫,但需要其它方法代替

有什么方法可以預(yù)防或者減少類似的問題發(fā)生呢渗鬼?答案是肯定的:ViewBinding

2.什么是ViewBinding

View Binding是Android Studio 3.6 推出的新特性,旨在替代findViewById(內(nèi)部實(shí)現(xiàn)還是使用findViewById)荧琼。算是一個語法糖譬胎。
通過ViewBinding差牛,可以更輕松地編寫可與視圖交互的代碼。在模塊中啟用ViewBinding之后银择,系統(tǒng)會為該模塊中的每個 XML 布局文件生成一個綁定類多糠。綁定類的實(shí)例包含對在相應(yīng)布局中具有 ID 的所有視圖的直接引用。

在大多數(shù)情況下浩考,ViewBinding可以做到替代 findViewById夹孔。

3.ViewBinding怎么使用

啟用Viewbinding功能:在模塊build.gradle文件android節(jié)點(diǎn)下添加如下代碼

android {
  ......
  buildFeatures {
      viewBinding true
  }
}

重新編譯后系統(tǒng)會為每個布局文件生成對應(yīng)的Binding類。該類中包含對應(yīng)布局中具有 ID 的所有視圖的直接引用析孽。

文件生成規(guī)則:將布局文件名轉(zhuǎn)成駝峰命名并添加以Binding結(jié)尾的類搭伤。例如activity_main.xml生成的類是ActivityMainBinding.java。類的字段是對應(yīng)布局文件中的id袜瞬。字段名生成規(guī)則和類名類似怜俐。

生成類的目錄在模塊根目錄/build/generated/data_binding_base_class_source_out下。如下圖:


生成文件示例

3.1 在Activity中使用

在MainActivity布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="come on" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

MainActivity中使用:

ActivityMainBinding mViewBinding;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  mViewBinding = ActivityMainBinding.inflate(getLayoutInflater());
  setContentView(mViewBinding.getRoot());
  ...
}

使用的時候在Activity的onCreate方法里調(diào)用其靜態(tài)inflate方法邓尤,返回ViewBinding實(shí)例拍鲤,通過ViewBinding實(shí)例可以直接訪問布局文件中帶id的控件,比如上面的TextView汞扎,mViewBinding.tvTextView

如果想在生成綁定類時忽略某個布局文件季稳,將tools:viewBindingIgnore="true"屬性添加到相應(yīng)布局文件的根視圖中。

3.2 在Fragment中使用

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    FragmentViewBindBinding binding = FragmentViewBindBinding.inflate(inflater, container, false);
    return binding.getRoot();
}

3.3 在Dialog中的使用

public class MyDialog extends Dialog {

    protected View mView;
    protected DialogBottomBinding mBinding;
    
    public MyDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, themeResId);

        //原來的寫法
        mView = View.inflate(getContext(), getLayoutId(), null);

        //使用ViewBinding的寫法
        mBinding = DialogBottomBinding.inflate(getLayoutInflater());
        mView = mBinding.getRoot();
        
        setContentView(mView);
    }
}

3.4 在 Adapter 中的使用

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
  ItemViewBinding viewBinding = ItemViewBinding.inflate(LayoutInflater.from(mContext));
  return new MyViewHolder(viewBinding);
}

3.5 自定義View中使用

如果我們的自定義View中使用了layout布局澈魄,比如layout_my_view.xml景鼠,如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="這是自定義布局"
        android:textSize="50sp" />

</androidx.constraintlayout.widget.ConstraintLayout>

會生成一個名為LayoutMyViewViewBinding.java文件,在自定義View通過如下方式綁定痹扇,

public class MyView extends View {
    public MyView (Context context) {
      this(context, null);  
    }
    public MyView (Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public MyView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(getContext()), this, true);
    }
}

如果自定義View布局文件中使用merge標(biāo)簽铛漓,

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="這是自定義merge"
        android:textSize="50sp" />

</merge>

此時要寫成下面這個樣子,

LayoutMyViewViewBindingviewBinding=LayoutMyViewViewBinding.inflate(LayoutInflater.from(context),this);

include 標(biāo)簽的使用

include 標(biāo)簽不帶 merge 標(biāo)簽鲫构,需要給 include 標(biāo)簽添加 id, 直接使用 id 即可浓恶,用法如下所示。

<include
  android:id="@+id/include"
  layout="@layout/layout_include_item" />

ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
binding.include.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge");

include 標(biāo)簽帶 merge 標(biāo)簽芬迄,需要通過bind()將merge布局綁定到主布局上问顷,用法如下所示。

<include
  layout="@layout/layout_merge_item" />

ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
LayoutMergeItemBinding mergeItemBinding = LayoutMergeItemBinding.bind(binding.getRoot())禀梳;
mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge");

4.原理

原理就是Google在那個用來編譯的gradle插件中增加了新功能,當(dāng)某個module開啟ViewBinding功能后肠骆,編譯的時候就去掃描此模塊下的layout文件算途,生成對應(yīng)的binding類。

public final class ActivityMainBinding implements ViewBinding {
    @NonNull
    private final ConstraintLayout rootView;

    @NonNull
    public final TextView tvTextView;

    private ActivityMainBinding(@NonNull ConstraintLayout rootView,
                                            @NonNull TextView rvDataList) {
        this.rootView = rootView;
        this.tvTextView = tvTextView;
    }

    @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:
        {
            TextView tvTextView = rootView.findViewById(R.id.tv_text);
            if (tvTextView == null) {
                missingId = "tvTextView";
                break missingId;
            }
            return new ActivityMainBinding((ConstraintLayout) rootView, tvTextView);
        }
        throw new NullPointerException("Missing required view with ID: ".concat(missingId));
    }
}

可以看出蚀腿,最終使用的仍然是findViewById嘴瓤,和ButterKnife異曲同工扫外,不同的是ButterKnife通過編譯時注解生成ViewBinding類,而ViewBinding是通過編譯時掃描layout文件生成ViewBinding類廓脆。

5. 優(yōu)點(diǎn)

5.1 更早發(fā)現(xiàn)程序中部分錯誤:

發(fā)現(xiàn)程序錯誤的階段不同:findViewById發(fā)生在運(yùn)行時筛谚,ViewBinding作用在編譯期⊥7蓿可以更早期的語法和發(fā)現(xiàn)錯誤驾讲。

與使用 findViewById 相比,視圖綁定具有一些很顯著的優(yōu)點(diǎn):

Null 安全:由于視圖綁定會創(chuàng)建對視圖的直接引用席赂,因此不存在因視圖 ID 無效而引發(fā) Null 指針異常的風(fēng)險(xiǎn)吮铭。此外,如果視圖僅出現(xiàn)在布局的某些配置中(比如橫豎屏布局內(nèi)容差異)颅停,則綁定類中包含其引用的字段會使用 @Nullable 標(biāo)記谓晌。

類型安全:每個綁定類中的字段均具有與它們在 XML 文件中引用的視圖相匹配的類型。這意味著不存在發(fā)生類轉(zhuǎn)換異常的風(fēng)險(xiǎn)癞揉。

這些差異意味著布局和代碼之間的不兼容將會導(dǎo)致構(gòu)建在編譯時(而非運(yùn)行時)失敗纸肉。


大量預(yù)防性代碼,繁瑣冗余

5.2 使用更加方便簡潔喊熟,相同的功能柏肪,使用的編碼量更少

使用時findViewById需要提前聲明視圖,聲明過程冗余繁瑣逊移。ViewBinding直接使用预吆,簡潔明了,易于維護(hù)胳泉,更少的代碼實(shí)現(xiàn)相同的功能拐叉。


某類ViewBinding重構(gòu)前后代碼量對比

6.缺點(diǎn)

1、總代碼量并沒有減少扇商,機(jī)械化的代碼被自動工具生成凤瘦,我們可以直接使用
2、通過布局文件查找使用的地方變得相對困難案铺,需要手動輸入生成后的類名查找
3蔬芥、如果為了簡化binding對象的獲取需要用到反射實(shí)現(xiàn),影響部分性能(對現(xiàn)在市面上的手機(jī)這點(diǎn)性能應(yīng)該可以忽略不計(jì))
4控汉、如果為了取代ButterKnife笔诵,只能取代部分功能,針對ButterKnife點(diǎn)擊之類的姑子,需要轉(zhuǎn)成原始的setOnClickListner()來實(shí)現(xiàn)乎婿。用 lambda表達(dá)式的話,影響不大街佑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谢翎,一起剝皮案震驚了整個濱河市捍靠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌森逮,老刑警劉巖榨婆,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異褒侧,居然都是意外死亡良风,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門璃搜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拖吼,“玉大人,你說我怎么就攤上這事这吻〉醯担” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵唾糯,是天一觀的道長怠硼。 經(jīng)常有香客問我,道長移怯,這世上最難降的妖魔是什么香璃? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮舟误,結(jié)果婚禮上葡秒,老公的妹妹穿的比我還像新娘。我一直安慰自己嵌溢,他們只是感情好眯牧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赖草,像睡著了一般学少。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秧骑,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天版确,我揣著相機(jī)與錄音,去河邊找鬼乎折。 笑死绒疗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骂澄。 我是一名探鬼主播忌堂,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酗洒!你這毒婦竟也來了士修?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤樱衷,失蹤者是張志新(化名)和其女友劉穎棋嘲,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矩桂,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沸移,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了侄榴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雹锣。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖癞蚕,靈堂內(nèi)的尸體忽然破棺而出蕊爵,到底是詐尸還是另有隱情,我是刑警寧澤桦山,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布攒射,位于F島的核電站,受9級特大地震影響恒水,放射性物質(zhì)發(fā)生泄漏会放。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一钉凌、第九天 我趴在偏房一處隱蔽的房頂上張望咧最。 院中可真熱鬧,春花似錦御雕、人聲如沸矢沿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咨察。三九已至,卻和暖如春福青,著一層夾襖步出監(jiān)牢的瞬間摄狱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工无午, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媒役,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓宪迟,卻偏偏與公主長得像酣衷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子次泽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容