[译]深刻研究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

相关文章
相关标签/搜索