原文:Exploring View Binding in Depth — Using ViewBinding with < include>, < merge>, adapters, fragments, and activities
做者:Somesh Kumar
譯者:Fly_with24php
谷歌在2019 I/O 大會中的 What’s New in Architecture Components 介紹了 view binding
android
在 What’s New in Architecture Components 中,有一個簡短的關於view binding 的演講,演講中將 view binding 與現有解決方案進行了比較,並進一步討論了爲何view binding 比 data binding
或 Kotlin synthetics
等現有解決方案更好。git
對我而言,Kotlin synthetics
運行良好,可是沒有編譯時的安全性,這意味着全部 ID 都位於全局命名空間中。所以,若是您使用的 ID 具備相同的名稱,而且從錯誤的佈局導入 ID, 因爲ID不是當前佈局的一部分,致使崩潰,除非您將應用程序運行到該佈局,不然沒法提早知道這一點。github
這篇文章很好地概述了 Kotlin synthetics
的問題安全
The Argument Over Kotlin Syntheticsbash
View Binding
將在 Android Studio 3.6 穩定版中提供(譯者注:當前Android Studio穩定版版本爲3.5.3),若是您想要使用它,您能夠下載 Android Studio 3.6 RC3 或者 Android Studio 4.0 Canary 9app
view binding 的主要優勢是全部綁定類都是由Gradle插件生成的,所以它對構建時間沒有影響,而且具備編譯時安全性(咱們將在示例中看到)。ide
首先,啓用 view binding, 咱們須要在 module 的build.gradle文件中添加如下內容:函數
// Android Studio 3.6
android {
viewBinding {
enabled = true
}
}
// Android Studio 4.0
android {
buildFeatures {
viewBinding = true
}
}
複製代碼
注意:視圖綁定是逐模塊啓用的,所以,若是您具備多模塊項目設置,則須要在每一個 build.gradle
文件中添加以上代碼。工具
若是要在特定的佈局禁用 view binding,則須要在佈局文件的根視圖中添加 tools:viewBindingIgnore = 「true」
。
啓用後,咱們能夠當即開始使用它,而且當您完成同步 build.gradle 文件時,默認狀況下會生成全部綁定類。
它經過將XML佈局文件名轉換爲駝峯式大小寫並在其末尾添加 Binding
來生成綁定類。 例如,若是您的佈局文件名爲 activity_splash
,則它將生成綁定類爲 ActivitySplashBinding
。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivitySplashBinding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvVersionName.text = getString(R.string.version)
}
複製代碼
咱們有一個名爲 activity_splash
的佈局文件,裏面有一個ID爲 tvVersionName
的 TextView
,所以在使用view binding 時,咱們要作的就是獲取綁定類的引用,例如:
val binding: ActivitySplashBinding = ActivitySplashBinding.inflate(layoutInflater)
複製代碼
在 setContentView()
方法中使用 getRoot()
,該方法將返回佈局的根佈局。能夠從咱們建立的綁定類對象訪問視圖,而且能夠在建立對象後當即使用它,以下所示:
binding.tvVersionName.text = getString(R.string.version)
複製代碼
在這裏,綁定類知道 tvVersionName
是TextView
,所以咱們沒必要擔憂類型轉換。
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
_binding = null
}
}
複製代碼
在 fragment 中,使用 view binding 有些不一樣。 咱們須要傳遞 LayoutInflator
,ViewGroup
和一個 attachToRoot
布爾變量,這些變量是經過覆蓋 onCreateView
得到的。
咱們能夠經過調用 binding.root
返回 view。您還注意到,咱們使用了兩個不一樣的變量 binding
和 _binding
,而且 _binding
變量在 onDestroyView()
中設置爲null。
這是由於該 fragment 的生命週期與 activity 的生命週期不一樣,而且該fragment 能夠超出其視圖的生命週期,所以若是不將其設置爲null,則可能會發生內存泄漏。
另外一個變量經過 !!
使一個變量爲可空值而使另外一個變量爲非空值避免了空檢查。 。
class PaymentAdapter(private val paymentList: List<PaymentBean>) : RecyclerView.Adapter<PaymentAdapter.PaymentHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentHolder {
val itemBinding = RowPaymentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PaymentHolder(itemBinding)
}
override fun onBindViewHolder(holder: PaymentHolder, position: Int) {
val paymentBean: PaymentBean = paymentList[position]
holder.bind(paymentBean)
}
override fun getItemCount(): Int = paymentList.size
class PaymentHolder(private val itemBinding: RowPaymentBinding) : RecyclerView.ViewHolder(itemBinding.root) {
fun bind(paymentBean: PaymentBean) {
itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber
itemBinding.tvPaymentAmount.text = paymentBean.totalAmount
}
}
}
複製代碼
row_payment.xml
是咱們用於 RecyclerView
item 的佈局文件,對應生成的綁定類 RowPaymentBinding
。
如今,咱們所須要作的就是在onCreateViewHolder()
中調用 inflate()
方法生成 RowPaymentBinding
對象並傳遞到 PaymentHolder
主構造器中,並將 itemBinding.root
傳遞給 RecyclerView .ViewHolder()
構造函數。
<include>
標籤view binding 能夠與 <include>
標籤一塊兒使用。 佈局中一般包含兩種 <include>
標籤,帶或不帶<merge>
標籤。
<inlude>
不帶 <merge>
標籤咱們須要爲<include>
分配一個 ID,而後使用該 ID 來訪問包含佈局中的視圖。讓咱們來看一個例子。
app_bar.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" android:layout_width="match_parent" android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="0dp" android:layout_height="?actionBarSize" android:background="?colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼
main_layout.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" android:layout_width="match_parent" android:layout_height="match_parent">
<include android:id="@+id/appbar" layout="@layout/app_bar" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼
在上面的代碼中,咱們在佈局文件中包括了一個通用工具欄,<include>
有一個 android:id=「@+id/appbar」
ID,咱們將使用它從 app_bar.xml
中訪問工具欄並將其設置爲咱們的 action bar
。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: MainLayoutBinding = MainLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.appbar.toolbar)
}
複製代碼
<inlude>
帶 <merge>
標籤當在一個佈局中包含另外一個佈局時,咱們一般使用一個帶有 <merge>
標記的佈局,這有助於消除佈局嵌套。
placeholder.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:id="@+id/tvPlaceholder" android:layout_width="match_parent" android:layout_height="wrap_content" />
</merge>
複製代碼
fragment_order.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<include layout="@layout/placeholder" />
</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼
若是咱們嘗試爲該 <include>
提供ID,view binding 不會在綁定類中生成ID,所以咱們沒法像使用普通 include
那樣訪問視圖。
在這種狀況下,咱們有 PlaceholderBinding
,它是 placeholder.xml
(<merge>
佈局文件)的自動生成的類。咱們能夠調用其bind()
方法並傳遞包含它的佈局的根視圖。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentOrderBinding.inflate(layoutInflater, container, false)
placeholderBinding = PlaceholderBinding.bind(binding.root)
placeholderBinding.tvPlaceholder.text = getString(R.string.please_wait)
return binding.root
}
複製代碼
而後,咱們能夠從咱們的類(如 placeholderBinding.tvPlaceholder.text
)訪問 placeholder.xml
內部的視圖。
感謝閱讀。但願收到您的評論。
在 fragment 中使用 view binding 比較麻煩,譯者提供一個 BaseFragment
的封裝供你們參考
abstract class BaseFragment<T : ViewBinding>(layoutId: Int) : Fragment(layoutId) {
private var _binding: T? = null
val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = initBinding(view)
init()
}
/** * 初始化 [_binding] */
abstract fun initBinding(view: View): T
abstract fun init()
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
複製代碼
class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {
override fun initBinding(view: View): FragmentHomeBinding = FragmentHomeBinding.bind(view)
override fun init() {
binding.viewPager.adapter = SectionsPagerAdapter(this)
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.text = TAB_TITLES[position]
}.attach()
}
}
複製代碼
我是 fly_with24