啥?ViewBinding還能替換自定義View!

公司剛來了一個小夥伴,名叫小白,剛畢業的小夥子,這天茶餘飯後,聊天聊起了代碼複用的問題。確實,代碼複用,能夠說是咱們每個有理想的程序員的追求。因而想借機考考他。php

:說到代碼複用,那!Android開發中,佈局該如何複用呢?java

好比,像下面所示的這樣一個卡片設計,不少頁面都有用到,不可能每一個頁面都去寫一遍吧?如何能很好的實現複用呢?android

小白:西哥,你這個問題也太簡單了,雖然我才學Android不久,可是這個我仍是知道的,咱們都知道,Android 佈局中,有個一個<include /> 標籤,能夠飲用一個佈局。咱們能夠把這個複用的卡片寫成一個單獨的佈局,而後在每一個頁面使用<include />包含進來就行了呀!程序員

因而二話沒說,就是幹,立刻就開始寫起了代碼!web

首先,抽出一個公共的佈局叫card_item.xml代碼以下:面試

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="100dp"  xmlns:app="http://schemas.android.com/apk/res-auto"  app:cardCornerRadius="5dp"  android:layout_margin="10dp"  app:cardElevation="2dp"> <RelativeLayout  android:layout_width="match_parent"  android:layout_height="match_parent"  >  <ImageView  android:id="@+id/avatar"  android:layout_width="80dp"  android:layout_height="90dp"  android:src="@mipmap/logo"  android:scaleType="centerCrop"  android:layout_centerVertical="true"  android:layout_marginLeft="15dp"  />  <TextView  android:id="@+id/name"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:textColor="#333"  android:textSize="18sp"  android:layout_toRightOf="@+id/avatar"  android:layout_marginLeft="5dp"  android:layout_marginTop="10dp"  />  <TextView  android:id="@+id/des"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:textColor="#999"  android:textSize="12sp"  android:layout_below="@+id/name"  android:layout_toRightOf="@+id/avatar"  android:layout_marginLeft="5dp"  android:layout_marginTop="10dp"  /> </RelativeLayout> </androidx.cardview.widget.CardView> 複製代碼

接着,在每個使用該卡片設計的地方,使用<include /> 標籤將card_item.xml佈局引入進來。新建佈局文件fragment.xml,代碼以下:編程

<?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 layout="@layout/card_item" />  </LinearLayout> 複製代碼

而後新建一個Fragment,名叫MyFragment,代碼以下:微信

class MyFragment: Fragment() {
 private lateinit var avatar: ImageView  private lateinit var name: TextView  private lateinit var desc: TextView   override fun onCreateView(  inflater: LayoutInflater,  container: ViewGroup?,  savedInstanceState: Bundle?  ): View? {  val view = inflater.inflate(R.layout.my_fragment,container,false)  avatar = view.findViewById(R.id.avatar)  name = view.findViewById(R.id.name)  desc = view.findViewById(R.id.des)  return view  }   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  super.onViewCreated(view, savedInstanceState)  avatar.setImageResource(R.mipmap.logo)  name.text = "技術最TOP"  desc.text = "扒最前沿科技動態,聊最TOP編程技術~"  } } 複製代碼

而後運行一下,效果以下:app

而後,在其餘須要的頁面,如MyFragment2MyFragment3,按照前面的步驟,引入佈局綁定數據,就行了。編輯器

很是簡單,5分鐘就寫好了。小白略帶微笑的說到。

:嗯,小夥子不錯不錯,這樣確實能夠,佈局文件確實複用了,可是你看看你的Fragment啊,好比我有4個Fragment,MyFragment1MyFragment2,MyFragment3MyFragment4,那其實我每一個Fragment中的大部分代碼都是相同的。

以下:

// 聲明View
 private lateinit var avatar: ImageView  private lateinit var name: TextView  private lateinit var desc: TextView   // 綁定View  val view = inflater.inflate(R.layout.my_fragment1,container,false)  avatar = view.findViewById(R.id.avatar)  name = view.findViewById(R.id.name)  desc = view.findViewById(R.id.des)   // 綁定數據  avatar.setImageResource(R.mipmap.logo)  name.text = "技術最TOP"  desc.text = "扒最前沿科技動態,聊最TOP編程技術~" 複製代碼

上面這些樣板代碼看起來很難受啊,每一個頁面都要這樣寫,而且後期很差維護,好比,我CardView 裏面新增長一個View,那麼這些用到的頁面都得改。有沒有辦法能把這些樣板代碼也一塊兒複用呢?

小白有點迷惑,用手撓撓頭,如有所思。

自定義View包裝

不一下子,小白大叫一聲,我有辦法了!

小白:咱們能夠藉助自定義View來封裝一下,咱們把Fragment中的樣板代碼,抽到一個View 中去,而後提供一個API方法給外部來設置數據,每一個使用的地方,將<include /> 引入的佈局換成自定義的View, 而後在Fragment中調用API設置數據就能夠了。

小白一臉自豪,說幹就幹,又開始重構前面的代碼。

首先,將樣板代碼抽取一個View名叫CardItem,將聲明View、綁定View、綁定數據的邏輯都放在這裏,代碼以下:

class CardItem @JvmOverloads constructor(
 context: Context, attributes: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attributes, defStyleAttr) {   private var ivAvatar: ImageView  private var tvName: TextView  private var tvDesc: TextView   init {  val view = LayoutInflater.from(context).inflate(R.layout.card_item,null,false)  ivAvatar = view.findViewById(R.id.avatar)  tvName = view.findViewById(R.id.name)  tvDesc = view.findViewById(R.id.des)   addView(view)  }   fun setData(imageAvatarRes: Int, name: String, desc: String) {  ivAvatar.setImageResource(imageAvatarRes)  tvName.text = name  tvDesc.text = desc  } } 複製代碼

如上面代碼所示,咱們提供了一個方法setData來綁定數據。

而後使用的地方,先替換佈局文件的<include /> ,代碼以下:

<?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 layout="@layout/card_item" />-->   <com.jay.jetpack.viewbinding.CardItem  android:id="@+id/card_item"  android:layout_width="match_parent"  android:layout_height="wrap_content" /> </Line 複製代碼

而後在Fragment中,調用setData綁定數據

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 super.onViewCreated(view, savedInstanceState)  cardItem.setData(imageAvatarRes = R.mipmap.logo,name="技術最TOP",desc = "扒最前沿科技動態,聊最TOP編程技術~")  } 複製代碼

運行一下代碼,效果以下圖所示:

才過10分鐘,小白就把代碼重構好了。

我: 不錯不錯,小夥子,這種方案很好,幾乎大部分代碼都公用了。

可是不夠完美,有一個小問題,你看這個自定義View類,裏面一樣是不少樣板代碼,若是咱們又有另外一個佈局須要公用,那麼我可能就須要再添加一個自定義View,把CardItem裏面的代碼拷貝過去,而後改吧改吧,改爲對應的佈局和View,當項目愈來愈大的時候,這種自定義View可能就越多。可是他們的大部分代碼實際上是相同的。

有沒有辦法可以解決這個問題,把這裏面的樣板代碼也消除呢?

小白又陷入了沉思!

小白:這我真不知道了,還有什麼辦法?西哥給我講講唄。

:你有據說過ViewBinding嗎?

小白:聽過聽過!就是Google 最新出的Jetpack組件嘛,江湖上聲稱幹掉findViewById,取代黃油刀ButterKnife的大殺器。

:對,就是這個,咱們能夠用這個,加上Kotlin 的特性來作更完美的優化。

ViewBingding的救贖

ViewBinding是Jetpack中新添加的組件,首先,在build.gradle中開啓:

viewBinding {
        enabled = true
 }
複製代碼

開啓ViewBinding後,他會自動幫個人佈局生成對應的類,好比咱們上面的card_item.xml,會給我生成一個CardItemBinding.java類,my_fragment2.xml會生成MyFragment2Binding.java,生成規則爲:佈局文件的名字去掉下劃線 + Binding後綴,以駝峯的形式。以下:

首先,把佈局中的<CardItem /> 換成 <include /> 標籤。代碼以下:

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

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

</LinearLayout>
複製代碼

而後,咱們就能夠不用findViewById()來綁定View了,能夠直接使用xxBinding類訪問View,Fragment代碼以下:

class MyFragment2: Fragment(R.layout.my_fragment2) {
 private lateinit var binding: MyFragment2Binding   override fun onCreateView(  inflater: LayoutInflater,  container: ViewGroup?,  savedInstanceState: Bundle?  ): View? {  binding = MyFragment2Binding.inflate(inflater, container, false)  return binding.root  }   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  super.onViewCreated(view, savedInstanceState)  binding.topCard.apply {  avatar.setImageResource(R.mipmap.logo)  name.text="技術最TOP"  des.text = "扒最前沿科技動態,聊最TOP編程技術。"  }  } } 複製代碼

這樣,咱們就把一個幾十行代碼的自定義View類,變成了4代碼,是否是就很是爽了。先別高興,還有點問題,雖然咱們去掉了樣板代碼,可是仍是存在咱們最初的那個問題,那就是,若是複用的佈局增長或者減小View的話,那麼在每一個調用的地方都要更改。 這可不是咱們想要的,怎麼解決這個問題呢?

還好有Kotlin,咱們能夠用Kotlin的擴展函數來優化!

Kotlin擴展函數 + ViewBinding

咱們把綁定數據的那一段代碼,抽一個擴展函數:

fun CardItemBinding.bind(imageResId: Int,nameStr: String, descStr: String){
 avatar.setImageResource(imageResId)  name.text = nameStr  des.text = descStr } 複製代碼

咱們在CardItemBinding上擴展了一個bind方法。

如今咱們如何調用了?下面這樣:

binding.topCard.bind(imageResId = R.mipmap.logo,
 nameStr = "技術最TOP Super",  descStr = "扒最前沿科技動態,聊最TOP編程技術。Super") 複製代碼

運行一下,效果以下:

完美實現,咱們把自定義View,替換成了一個ViewBinding的擴展函數,代碼從原來的33行,減小到了如今的4行。

後期維護也很方便,增長減小View,直接在擴展方法裏面更改就好。

而且,若是還有其餘的複用佈局,咱們再添加一個擴展方法就行了,這就很是爽了!

小白:啥?等於說,利用Kotin + ViewBinding 能夠替換自定義View了?妙啊!

我也去寫一個來試試!

文章首發於公衆號:「 技術最TOP 」,天天都有乾貨文章持續更新,能夠微信搜索「 技術最TOP 」第一時間閱讀,回覆【思惟導圖】【面試】【簡歷】有我準備一些Android進階路線、面試指導和簡歷模板送給你

相關文章
相關標籤/搜索