[譯]深刻研究ViewBinding 在 < include>, < merge>, adapter, fragment, activity 中使用

原文:Exploring View Binding in Depth — Using ViewBinding with < include>, < merge>, adapters, fragments, and activities
做者:Somesh Kumar
譯者:Fly_with24php


Image Source: Google I/O 2019

谷歌在2019 I/O 大會中的 What’s New in Architecture Components 介紹了 view bindingandroid

What’s New in Architecture Components 中,有一個簡短的關於view binding 的演講,演講中將 view binding 與現有解決方案進行了比較,並進一步討論了爲何view binding 比 data bindingKotlin 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

如何使用它?

activity 中使用

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爲 tvVersionNameTextView ,所以在使用view binding 時,咱們要作的就是獲取綁定類的引用,例如:

val binding: ActivitySplashBinding = ActivitySplashBinding.inflate(layoutInflater) 
複製代碼

setContentView() 方法中使用 getRoot() ,該方法將返回佈局的根佈局。能夠從咱們建立的綁定類對象訪問視圖,而且能夠在建立對象後當即使用它,以下所示:

binding.tvVersionName.text = getString(R.string.version)
複製代碼

在這裏,綁定類知道 tvVersionNameTextView,所以咱們沒必要擔憂類型轉換。

fragment 中使用

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 有些不一樣。 咱們須要傳遞 LayoutInflatorViewGroup和一個 attachToRoot 布爾變量,這些變量是經過覆蓋 onCreateView 得到的。

咱們能夠經過調用 binding.root 返回 view。您還注意到,咱們使用了兩個不一樣的變量 binding_binding,而且 _binding 變量在 onDestroyView() 中設置爲null。

這是由於該 fragment 的生命週期與 activity 的生命週期不一樣,而且該fragment 能夠超出其視圖的生命週期,所以若是不將其設置爲null,則可能會發生內存泄漏。

另外一個變量經過 !! 使一個變量爲可空值而使另外一個變量爲非空值避免了空檢查。 。

在 RecyclerView adapter 中使用

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

相關文章
相關標籤/搜索