kotlin-android-extensions插件也被廢棄了?扶我起來

本文同步發表於個人微信公衆號,掃一掃文章底部的二維碼或在微信搜索 郭霖 便可關注,每一個工做日都有文章更新。java

kotlin-android-extensions插件可能算得上是我最喜歡的一個Kotlin在Android上的特性了。android

這麼說並不誇張,由於之前在使用Java開發Android程序時,咱們老是要寫一大堆的findViewById,枯燥又沒什麼意義。面試

雖然也有一些諸如ButterKnife之類的第三方庫,專門用於對findViewById的用法進行簡化,可是ButterKnife仍是要經過註解來讓控件與資源id之間進行綁定,並不算是很是方便。緩存

而kotlin-android-extensions插件的出現則讓這一狀況徹底發生了改變,咱們能夠不用再編寫煩瑣的findViewById代碼,同時能用一種很是簡便的寫法進行替代。微信

好比說這裏有一個佈局文件activity_main.xml:markdown

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <TextView android:id="@+id/viewToShowText" android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>
複製代碼

很是簡單,佈局文件中只有一個TextView控件,它的id是viewToShowText。數據結構

那麼,若是我想要在MainActivity中去設置TextView控件的內容,使用Java語言的話一般須要這樣寫:ide

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView viewToShowText = findViewById(R.id.viewToShowText);
        viewToShowText.setText("Hello");
    }
    
}
複製代碼

能夠看到,這裏咱們首先經過findViewById()函數獲取到了TextView控件的實例,而後再調用setText()函數將其顯示的內容設置成Hello。函數

這個findViewById()函數實際上是很頭疼的,這裏咱們只是獲取了一個控件的實例,因此可能感覺還不太明顯。若是你要去獲取10個甚至100個控件的實例,每一個都要去findViewById一遍,你必定會抓狂的。oop

那麼若是是使用Kotlin語言的話,這個問題要怎麼解決呢?藉助kotlin-android-extensions插件,咱們可使用以下代碼來完成一樣的功能:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewToShowText.text = "Hello"
    }
    
}
複製代碼

能夠看到,這裏咱們再也不須要調用findViewById()函數去獲取控件的實例,而是直接調用該控件在xml中定義的id名稱,就可以設置其顯示的內容了。

而這個神奇的功能就是由kotlin-android-extensions插件自動完成的,這個插件可以幫助咱們減小大量瑣碎無心義的代碼。

然而它被廢棄了

其實早在幾個月前,就有朋友在公衆號上詢問我,說本身升級了Android Studio 4.1以後,發現新建項目的時候Android Studio已經不會自動幫咱們引入kotlin-android-extensions插件了,須要本身手動去添加才能使用,是否是Google再也不推薦使用這個插件了?

當時我還說,不可能呀,這個插件這麼好用,並且Kotlin也是Google將來主推的技術,可能只是Android Studio 4.1的bug吧。

然而,沒過多久我就被打臉了。某天我將項目工程的Gradle版本升級到了最新,而後構建項目時發現了這樣一個警告提示:

圖1

Google明確地告訴咱們,kotlin-android-extensions插件已被廢棄,如今推薦使用ViewBinding來進行替代。

對於Google的這種技術迭代頻率我是有點生氣的,若是kotlin-android-extensions插件是Google主推的技術,理應擁有更長的生命週期,否則的話就不應做爲默認插件 集成到Android Studio當中。要知道,去年我纔剛剛出版的新書《第一行代碼 第3版》裏還大量使用了這個技術。

不過,好在ViewBinding並不複雜,從kotlin-android-extensions插件切換到ViewBinding也是比較容易的,那麼本篇文章就做爲《第一行代碼 第3版》的另一篇DLC,向你們介紹一下,如何使用ViewBinding來替代kotlin-android-extensions插件。

爲何會被廢棄

在開始介紹ViewBinding以前,我仍是想先討論一下,爲何kotlin-android-extensions插件會被廢棄。

雖然說Google的技術迭代頻率經常會讓咱們直呼學不動了,可是Google也絕對不會平白無故去廢棄一個以前主推的技術,說明kotlin-android-extensions插件確定仍是存在問題的。

那麼到底存在什麼問題呢?

比較容易讓人想到的一個缺點就是,kotlin-android-extensions插件只能支持Kotlin語言,而沒法支持Java語言。固然這個我認爲並非主要緣由,由於如今Google開發的各類新技術都在全面兼容Kotlin,而再也不怎麼去考慮Java了,如協程、Jetpack Compose等。

那麼主要緣由是什麼呢?這可能就要從kotlin-android-extensions插件的實現原理去理解了。剛纔咱們已經看到過了使用kotlin-android-extensions插件後的代碼,很是簡單:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewToShowText.text = "Hello"
    }
    
}
複製代碼

那麼這段代碼爲何能夠工做呢?

咱們能夠經過點擊Android Studio頂部導航欄的Tools -> Kotlin -> Show Kotlin Bytecode來查看這段代碼對應的Kotlin字節碼,而後在彈出窗口中點擊Decompile按鈕將字節碼反編譯成Java代碼。

爲了方便閱讀,我將反編譯後的代碼又作了些整理,大體以下所示:

public final class MainActivity extends AppCompatActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300023);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.textView);
      var10000.setText((CharSequence)"Hello");
   }

   public View _$_findCachedViewById(int var1) {
      if (this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }
      View var2 = (View)this._$_findViewCache.get(var1);
      if (var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(var1, var2);
      }
      return var2;
   }
}
複製代碼

能夠看到,實際上kotlin-android-extensions插件會幫咱們生成一個_$_findCachedViewById()函數(使用這種奇怪的命名方式是爲了防止和開發者定義的函數名衝突)。在這個函數中首先會嘗試從一個HashMap中獲取傳入的資源id參數所對應的控件實例緩存,若是尚未緩存的話,就調用findViewById()函數來查找控件實例,並寫入HashMap緩存當中。這樣當下次再獲取相同控件實例的話,就能夠直接從HashMap緩存中獲取了。

這就是kotlin-android-extensions插件的實現原理,其實仍是很是簡單的。

然而這種實現原理同時也暴露出來了一些問題。

好比說每個Activity都須要使用一個額外的HashMap數據結構來存儲全部控件的實例,無形中增長了一些內存的開支。

還有,雖然HashMap是一種O(1)時間複雜度的數據結構,但這畢竟只是理論上的時間複雜度,實際調用確定是沒有直接訪問控件實例快的,所以kotlin-android-extensions插件也在無形當中下降了程序的運行效率。

最重要的是,這些內容對於絕大部分開發者來講都是黑盒,使用kotlin-android-extensions插件的人可能並不知道這些隱藏的「坑」,這個問題在稍後介紹RecyclerView Adapter的時候會更加突顯,咱們待會兒再聊。

無論我上面分析的這些足不足以成爲廢棄kotlin-android-extensions插件的理由,總之這已是事實了。那麼接下來,咱們的學習目標就變成了:如何使用ViewBinding來替代以前的kotlin-android-extensions插件。請放心,這並非一件很難的事情。

什麼是ViewBinding

ViewBinding整體來講其實很是簡單,它的目的只有一個,就是爲了不編寫findViewById,這和它另一個很是複雜的兄弟DataBinding相比有明顯的區別。

要想使用ViewBinding須要注意兩件事。第一,確保你的Android Studio是3.6或更高的版本。第二,在你項目工程模塊的build.gradle中加入如下配置:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}
複製代碼

這樣準備工做就完成了。接下來我會從Activity、Fragment、Adapter、引入佈局這4個方面,分別討論ViewBinding的用法。

在Activity中使用ViewBinding

一旦啓動了ViewBinding功能以後,Android Studio會自動爲咱們所編寫的每個佈局文件都生成一個對應的Binding類。

Binding類的命名規則是將佈局文件按駝峯方式重命名後,再加上Binding做爲結尾。

好比說,前面咱們定義了一個activity_main.xml佈局,那麼與它對應的Binding類就是ActivityMainBinding。

固然,若是有些佈局文件你不但願爲它生成對應的Binding類,能夠在該佈局文件的根元素位置加入以下聲明:

<LinearLayout xmlns:tools="http://schemas.android.com/tools" ... tools:viewBindingIgnore="true">
    ...
</LinearLayout>
複製代碼

接下來咱們看一下如何使用ViewBinding來實如今MainActivity中去設置TextView內容的功能,代碼以下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.textView.text = "Hello"
    }

}
複製代碼

ViewBinding的用法能夠說就是這麼簡單。首先咱們要調用activity_main.xml佈局文件對應的Binding類,也就是ActivityMainBinding的inflate()函數去加載該佈局,inflate()函數接收一個LayoutInflater參數,在Activity中是能夠直接獲取到的。

接下來就更加簡單了,調用Binding類的getRoot()函數能夠獲得activity_main.xml中根元素的實例,調用getTextView()函數能夠得到id爲textView的元素實例。

那麼很明顯,咱們應該把根元素的實例傳入到setContentView()函數當中,這樣Activity就能夠成功顯示activity_main.xml這個佈局的內容了。而後獲取TextView控件的實例,並給它設置要顯示的文字便可。

固然,若是你須要在onCreate()函數以外的地方對控件進行操做,那麼就得將binding變量聲明成全局變量,寫法以下:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.textView.text = "Hello"
    }

}
複製代碼

注意,Kotlin聲明的變量都必須在聲明的同時對其進行初始化。而這裏咱們顯然沒法在聲明全局binding變量的同時對它進行初始化,因此這裏又使用了lateinit關鍵字對binding變量進行了延遲初始化。

雖然這裏我舉的例子很是簡單,但實際上ViewBinding用法的套路都是如此,掌握了這一套規則以後基本上你就能夠觸類旁通了。

在Fragment中使用ViewBinding

下面咱們學習一下,如何在Fragment中使用ViewBinding。這部份內容一樣很是簡單,由於在Fragment中使用ViewBinding和在Activity基本是同樣的。

這裏我仍是經過代碼的方式進行演示,順便介紹一下Fragment與Activity中ViewBinding用法的異同。

假設咱們有一個佈局文件叫fragment_main.xml,那麼啓用ViewBinding功能以後,則必然會生成一個與其對應的FragmentMainBinding類。

若是咱們想要在MainFragment中去顯示這個佈局,就能夠這樣寫:

class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null

    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}
複製代碼

這段代碼的實際邏輯遠沒有看上去得複雜。

首先最核心的邏輯仍然是調用FragmentMainBinding的inflate()函數去加載fragment_main.xml佈局文件,但因爲這是在Fragment當中,因此使用了3個參數的inflate()函數重載,這和咱們平時在Fragment中去加載佈局文件的方式一模一樣。

接下來不同的地方在於,因爲咱們是在onCreateView()函數中加載的佈局,那麼理應在與其對應的onDestroyView()函數中對binding變量置空,從而保證binding變量的有效生命週期是在onCreateView()函數和onDestroyView()函數之間。

但因爲Kotlin空類型系統的存在,致使爲了實現這一簡單的功能,須要額外編寫一些看上去很奇怪的代碼,上述代碼就是如此。關於Kotlin空類型系統這裏我就不展開介紹了,還不瞭解的朋友能夠去參考《第一行代碼 第3版》第2章的內容。

好吧,這是我少有認可Java要比Kotlin更簡潔的地方,由於使用Java代碼去實現一樣的功能只須要這樣寫:

public class MainFragment extends Fragment {
    
    private FragmentMainBinding binding;

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

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}
複製代碼

這兩段代碼實現的功能以及所想表達的意思是徹底相同的,可是明顯Java版本要更加好理解一點。

在Adapter中使用ViewBinding

接下來,讓咱們再來探討一下在Adapter中使用ViewBinding的場景,這個場景會相對比較有趣,同時也是以前kotlin-android-extensions插件被誤用比較多的地方。

相信每一位Android開發者都使用過RecyclerView,也都寫過Adapter,Adapter實際上是很能考驗一個開發者功底的地方。

我在很早以前面試的時候被問到過,爲何咱們要在ListView的Adapter當中去寫ViewHolder(那個時候尚未RecyclerView)。答案就是,爲了避免用在列表滾動的時候頻繁調用findViewById(),從而減小了一些不必的性能消耗。

而RecyclerView把ListView中的這個廣泛應用的最佳實踐直接做爲默認實現集成了進去,因此只要咱們使用RecyclerView,就必定要寫ViewHolder。

然而有些朋友在這裏卻存在一些誤用的狀況,這裏我仍是經過一個具體的示例進行說明。

假設咱們定義了fruit_item.xml來做爲RecyclerView子項的佈局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp">

    <ImageView android:id="@+id/fruitImage" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center_horizontal" android:layout_marginTop="10dp" />

    <TextView android:id="@+id/fruitName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:layout_marginTop="10dp" />

</LinearLayout>
複製代碼

而後編寫以下RecyclerView Adapter來加載和顯示這個子項佈局:

class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName: TextView = view.findViewById(R.id.fruitName)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId)
        holder.fruitName.text = fruit.name
    }

    override fun getItemCount() = fruitList.size

}
複製代碼

這是比較標準和傳統的一種寫法,而且能夠說沒有任何問題,《第一行代碼 第3版》中關於RecyclerView這部分講解也是使用的這種寫法。

然而有些讀者朋友跟我反饋,說這種寫法還要在ViewHolder當中聲明控件變量,還要編寫findViewById(),實在是太複雜了。本身找到了一種更簡單的寫法,只須要藉助kotlin-android-extensions插件,就能夠這樣寫:

class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.itemView.fruitImage.setImageResource(fruit.imageId)
        holder.itemView.fruitName.text = fruit.name
    }

    override fun getItemCount() = fruitList.size

}
複製代碼

能夠看到,這裏ViewHolder中沒有進行任何控件聲明,至關於只是定義了一個空的ViewHolder。而後在onBindViewHolder()函數當中,直接調用holder.itemView再接上控件id的名稱就可使用它了。

這種寫法確實簡化了很多代碼,可是這種寫法對嗎?

若是你的評判標準只是這段代碼能不能正常工做,那麼答案是確定的,這樣寫確實能夠正常工做。可是這種寫法我能夠說是徹底不正確的,爲何呢?咱們只須要使用剛纔的手法把這段代碼反編譯一下,看看它對應的Java代碼是什麼樣的就知道了。

一樣爲了方便閱讀,我仍是對代碼進行了簡化,只保留了關鍵部分,以下所示:

public final class FruitAdapter extends Adapter {
   ...

   public final class ViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
      public ViewHolder(@NotNull View view) {
         super(view);
      }
   }

   public void onBindViewHolder(@NotNull FruitAdapter.ViewHolder holder, int position) {
      Fruit fruit = (Fruit)this.fruitList.get(position);
      View var10000 = holder.itemView;
      ((ImageView)var10000.findViewById(id.fruitImage)).setImageResource(fruit.getImageId());
      var10000 = holder.itemView;
      TextView var4 = (TextView)var10000.findViewById(id.fruitName);
      var4.setText((CharSequence)fruit.getName());
   }

}
複製代碼

不知道你有沒有發現問題,如今onBindViewHolder()函數當中,每次都是調用了findViewById()來獲取控件實例,這樣就致使ViewHolder的做用徹底失效了。

因此,上面這種寫法就是kotlin-android-extensions插件在Adapter當中一種比較典型的誤用方式。同時也算是一個隱藏的「坑」,由於若是你不去將Kotlin代碼進行反編譯,可能都不知道本身的ViewHolder其實根本就沒有起到任何做用。

講完了kotlin-android-extensions插件的「坑」,接下來咱們仍是看一下如何在Adapter中使用ViewBinding,別忘了咱們的目標始終是不寫findViewById。

其實若是你已經熟練掌握了ViewBinding在Activity和Fragment中的用法,那麼如今應該能夠觸類旁通了,由於在Adapter中使用ViewBinding基本也是一樣的套路。

咱們仍是先來看一下代碼,而後我再稍微作下簡單的講解:

class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {

    inner class ViewHolder(binding: FruitItemBinding) : RecyclerView.ViewHolder(binding.root) {
        val fruitImage: ImageView = binding.fruitImage
        val fruitName: TextView = binding.fruitName
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = FruitItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId)
        holder.fruitName.text = fruit.name
    }

    override fun getItemCount() = fruitList.size

}
複製代碼

這段代碼的核心基本都在onCreateViewHolder()函數和ViewHolder當中。

首先,咱們在onCreateViewHolder()函數中調用FruitItemBinding的inflate()函數去加載fruit_item.xml佈局文件,這和ViewBinding在Fragment中的用法是如出一轍的。

接下來須要改造ViewHolder,讓其構造函數接收FruitItemBinding這個參數。可是注意,ViewHolder的父類RecyclerView.ViewHolder它只會接收View類型的參數,所以咱們須要調用binding.root得到fruit_item.xml中根元素的實例傳給RecyclerView.ViewHolder。

這樣,咱們就不須要再使用findViewById()函數來查找控件實例了,而是調用binding.fruitImage和binding.fruitName就能夠直接引用到相應控件的實例。

這就是ViewBinding在Adapter中的用法。

對引入佈局使用ViewBinding

關於ViewBinding的使用其實還有另一種比較特殊的場景,那就是如何對引入佈局使用ViewBinding。

引入佈局通常有兩種方式,include和merge。關於這兩種方式的用法和區別,我在 Android最佳性能實踐(四)——佈局優化技巧 這篇文章中有比較詳細的講解,還不瞭解的朋友能夠去參考一下。

接下來咱們開始分別學習如何在include和merge的佈局中使用ViewBinding。

先來看include,這個狀況比較簡單。假設咱們有以下titlebar.xml佈局,是但願做爲一個通用佈局引入到各佈局當中的:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
 
    <Button android:id="@+id/back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="Back" />
 
    <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Title" android:textSize="20sp" />
 
    <Button android:id="@+id/done" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="Done" />
 
</RelativeLayout>
複製代碼

那麼若是我想要在activity_main.xml中引入這個佈局,只須要這樣寫:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
 
    <include layout="@layout/titlebar" />
    ...
</LinearLayout>
複製代碼

這種寫法雖然的確能夠將titlebar.xml引入到activity_main.xml佈局當中,但問題是,你會發現ViewBinding是關聯不到titlebar.xml中的控件的。

那麼如何解決這個問題呢?很簡單,咱們只須要在include的時候給被引入的佈局添加一個id,以下所示:

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

    <include android:id="@+id/titleBar" layout="@layout/titlebar" />
    ...
</LinearLayout>
複製代碼

而後,在MainActivity中,咱們便可經過以下的寫法引用到titlebar.xml中定義的控件:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.titleBar.title.text = "Title"
        binding.titleBar.back.setOnClickListener {
        }
        binding.titleBar.done.setOnClickListener {
        }
    }

}
複製代碼

接下來咱們再來看一下merge。merge和include最大的區別在於,使用merge標籤引入的佈局在某些狀況下能夠減小一層佈局的嵌套,而更少的佈局嵌套一般就意味着更高的效率。

好比說咱們對titlebar.xml進行以下修改:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
    <Button android:id="@+id/back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="Back" />

    <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Title" android:textSize="20sp" />

    <Button android:id="@+id/done" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="Done" />
 
</merge>
複製代碼

能夠看到,這裏最外層的佈局使用了merge標籤,這就表示當有任何一個地方去include這個佈局時,會將merge標籤內包含的內容直接填充到include的位置,不會再添加任何額外的佈局結構。

可是很遺憾,若是使用這種寫法的話,運行程序將會直接崩潰。由於merge標籤並非一個佈局,因此咱們沒法像剛纔那樣在include的時候給它指定一個id。

那麼這種狀況下應該怎麼使用ViewBinding呢?首先爲了不崩潰,咱們應該將activity_main.xml中引入佈局時指定的id移除,以下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

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

</LinearLayout>
複製代碼

而後修改MainActivity中的代碼,以下所示:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var titlebarBinding: TitlebarBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        titlebarBinding = TitlebarBinding.bind(binding.root)
        setContentView(binding.root)
        titlebarBinding.title.text = "Title"
        titlebarBinding.back.setOnClickListener {
        }
        titlebarBinding.done.setOnClickListener {
        }
    }

}
複製代碼

能夠看到,這裏咱們又定義了一個titlebarBinding變量。很明顯,TitlebarBinding就是Android Studio根據咱們的titlebar.xml佈局文件自動生成的Binding類。

在onCreate()函數中,咱們調用TitlebarBinding.bind()函數,讓titlebar.xml佈局和activity_main.xml佈局可以關聯起來。

接下來的事情就很簡單了,直接使用titlebarBinding變量就能夠引用到titlebar.xml中定義的各個控件了。

好了,這大概就是關於ViewBinding的全部內容了,至少我已經想不出還有什麼更多的用法,相信本篇文章也足以將你工做中可能遇到的ViewBinding相關的問題所有覆蓋到。

另外,若是想要學習Kotlin和最新的Android知識,能夠參考個人新書 《第一行代碼 第3版》點擊此處查看詳情


關注個人技術公衆號「郭霖」,每一個工做日都有優質技術文章推送。

相關文章
相關標籤/搜索