公司剛來了一個小夥伴,名叫小白
,剛畢業的小夥子,這天茶餘飯後,聊天聊起了代碼複用的問題。確實,代碼複用,能夠說是咱們每個有理想的程序員的追求。因而想借機考考他。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
而後,在其餘須要的頁面,如MyFragment2
、MyFragment3
,按照前面的步驟,引入佈局
,綁定數據
,就行了。編輯器
很是簡單,5分鐘就寫好了。小白
略帶微笑的說到。
我
:嗯,小夥子不錯不錯,這樣確實能夠,佈局文件
確實複用了,可是你看看你的Fragment啊,好比我有4個Fragment,MyFragment1
、MyFragment2
,MyFragment3
、MyFragment4
,那其實我每一個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來封裝一下,咱們把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 的特性來作更完美的優化。
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的擴展函數來優化!
咱們把綁定數據的那一段代碼,抽一個擴展函數:
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進階路線、面試指導和簡歷模板送給你