ViewBinding使用入门

Posted cfy137000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewBinding使用入门相关的知识,希望对你有一定的参考价值。

ViewBinding

参考资料:
新技术 ViewBinding 最佳实践 & 原理击穿
更多 ViewBinding 的封装思路

1. kotlin-android-extensions(KAE) 的问题

根据Google官方的说法, KAE存在以下问题:

  • 污染全局命名空间
  • 不能暴露可空性信息
  • 仅支持Kotlin代码

kotlin在1.4.20中 开始废弃这个库了(https://blog.jetbrains.com/kotlin/2020/11/kotlin-1-4-20-released/#Deprecation_of_Kotlin_Android_Extensions)
ReleaseNote中的原因是 KAT所填补的空白已经被ViewBinding所代替了, 所以他们不再支持了…

2. ViewBinding的使用

1. 开启ViewBinding

在model的build.gradle 中开启

android 
    viewBinding 
        enabled = true
    

之后每个xml布局都会生成对应ViewBinding类

如果想要忽略摸个布局文件, 可以添加tools:viewBindingIgnore="true"属性

<LinearLayout
            ...
            tools:viewBindingIgnore="true" >
        ...
    </LinearLayout>

2. 基本用法

生成的ViewBinding类使用以下方式的命名规则:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词

例如, 有如下布局 名称为result_profile.xml:

<LinearLayout ... >
        <TextView android:id="@+id/name" />
        <ImageView android:cropToPadding="true" />
        <Button android:id="@+id/button"
            android:background="@drawable/rounded_button" />
</LinearLayout>

所生成的绑定类的名称就为 ResultProfileBinding。此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。

每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。

每一个Binding类都提供了3中创建ViewBinding对象的方式, 可以根据情况选择:

位置:

ViewBinding的用法基本上都是在围绕着上面的3个方法

2.1 在Activity中只用

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
    private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) 
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    

然后就可以使用对应的View了

    binding.name.text = viewModel.name
    binding.button.setOnClickListener  viewModel.userClicked() 

2.2 在Fragment中使用

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Fragment 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 从 onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。
    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? 
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    

    override fun onDestroyView() 
        super.onDestroyView()
        _binding = null
    
    

2.3 在Adapter中使用ViewBinding

在Adapter中使用ViewBinding与之前没有什么不同

class SimpleDataAdapter : RecyclerView.Adapter<SimpleDataAdapter.ViewHolder>() 
    var dataList: List<Data> = emptyList()
        set(value) 
            field = value
            notifyDataSetChanged()
        

    inner class ViewHolder(binding: ItemDataBinding) : RecyclerView.ViewHolder(binding.root) 
        val dataIv = binding.dataIv
        val dataTv = binding.dataTv
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 
        val binding = ItemDataBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    

    override fun onBindViewHolder(holder: ViewHolder, position: Int) 
        dataList[position].let 
            holder.dataIv.setImageResource(it.img)
            holder.dataTv.text = it.txt
        
    

    override fun getItemCount(): Int = dataList.size

2.4 对引入布局使用ViewBinding

2.4.1 include

比如有如下布局: titlebar.xml 希望作为一个通用布局引入到其他布局中

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

    <Button
        android:id="@+id/back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:text="Back" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Title"
        android:textSize="20sp" />

    <Button
        android:id="@+id/done"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:text="Done" />

</RelativeLayout>

activity的布局:

<?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
        android:id="@+id/titleBar"
        layout="@layout/titlebar" />
</LinearLayout>

Activity中使用

class IncludeActivity : AppCompatActivity() 
    private lateinit var binding: ActivityIncludeBinding

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityIncludeBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.titleBar.title.text = "Title"
        binding.titleBar.back.setOnClickListener 
        
        binding.titleBar.done.setOnClickListener 
        
    


这里可以看到 binding中根据id生成了一个titleBar的对象, 查看ViewBinding生成的代码可知, 这个titleBar还是一个ViewBinding的类:

2.4.2 merge

如果被引入的布局根标签是merge, 则不能使用上述方式了, 疑问merge会将被引入的布局直接合并到对应的位置, 所以在ViewBinding中, 通过id找对应的ViewBinding的过程中会失败

相较于2.4.1的代码:

  1. 删除include标签上的id属性
  2. 在kotlin代码中, 自己赋值ViewBinding对象:
class MergeActivity : AppCompatActivity() 
    private lateinit var binding: ActivityMergeBinding
    private lateinit var titlebarBinding: TitlebarMergeBinding

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityMergeBinding.inflate(layoutInflater)
        setContentView(binding.root)
        titlebarBinding = TitlebarMergeBinding.bind(binding.root)
        titlebarBinding.title.text = "Title"
        titlebarBinding.back.setOnClickListener 
        
        titlebarBinding.done.setOnClickListener 
        
    


3. 封装

封装的主要思路是尽量不写,或少写一些模板代码, 主要的思路就是在创建ViewBinding对象上实现自动创建

3.1 Activity

open class BaseReflectionAty<VB : ViewBinding> : AppCompatActivity() 
    protected lateinit var binding: VB
        private set

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        generateViewBinding()
        if (this::binding.isInitialized) 
            setContentView(binding.root)
            binding.initViews()
        
    

    open fun VB.initViews() 

    /**
     * 生成ViewBinding
     */
    private fun generateViewBinding() 
        val type = this::class.java.genericSuperclass as? ParameterizedType ?: return
        val vbClazz = type.actualTypeArguments.find 
            // 找到泛型声明为 实现了 ViewBinding接口的类型
            (it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
         as? Class<*> ?: return

        val method = vbClazz.getMethod("inflate", LayoutInflater::class.java)
        binding = method.invoke(null, layoutInflater) as VB
    

在onCreate时, 通过generateViewBinding方法来生成对应的ViewBinding对象,使用时:

class MainActivity : BaseReflectionAty<ActivityMainBinding>() 
    override fun ActivityMainBinding.initViews() 
        mainTextView.setOnClickListener 
            Toast.makeText(this@MainActivity, "lalala", Toast.LENGTH_SHORT).show()
        
    

直接在initViews中使用布局文件中东的View即可

3.2 Fragment

Fragment的思路与Activity相同

class BaseReflectionFragment<VB : ViewBinding> : Fragment() 
    private var _binding: VB? = null
    protected val binding: VB
        get() = _binding!!


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? 
        generateViewBinding(container)

        if (_binding != null) 
            return binding.root
        
        return super.onCreateView(inflater, container, savedInstanceState)
    

    open fun VB.initViews() 

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        _binding?.initViews()
    

    override fun onDestroyView() 
        super.onDestroyView()
        _binding = null
    

    /**
     * 生成ViewBinding
     */
    private fun generateViewBinding(rootView: ViewGroup?) 
        val type = this::class.java.genericSuperclass as? ParameterizedType ?: return
        val vbClazz = type.actualTypeArguments.find 
            // 找到泛型声明为 实现了 ViewBinding接口的类型
            (it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
         as? Class<*> ?: return

        val method = vbClazz.getMethod(
            "inflate",
            LayoutInflater::class.java,
            ViewGroup::class.java,
            Boolean::class.java
        )
        _binding = method.invoke(null, layoutInflater, rootView, false) as VB
    

3.3 Adapter

ViewHolder:

class BaseViewHolder<VB : ViewBinding>(val binding: VB) : RecyclerView.ViewHolder(binding.root)

Adapter:

abstract class ViewBindingAdapter<VB : ViewBinding> : RecyclerView.Adapter<BaseViewHolder<VB>>() 
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<VB> 
        generateViewBinding(parent)?.let 
            return BaseViewHolder(it)
         ?: TODO("Not yet implemented")
    

    private fun generateViewBinding(parent: ViewGroup): VB? 
        val type = this::class.java.genericSuperclass as? ParameterizedType ?: return null
        val vbClazz = type.actualTypeArguments.find 
            // 找到泛型声明为 实现了 ViewBinding接口的类型
            (it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
         as? Class<*> ?: return null
        val layoutInflater = LayoutInflater.from(parent.context)

        val method = vbClazz.getMethod(
            "inflate",
            LayoutInflater::class.java,
            ViewGroup::class.java,
            Boolean::class.java
        )
        return method.invoke(null, layoutInflater, parent, false) as VB
    

以上是关于ViewBinding使用入门的主要内容,如果未能解决你的问题,请参考以下文章

Android 基于Jetpack的MVVM架构入门指南

Android 基于Jetpack的MVVM架构入门指南

android ViewBinding

android ViewBinding

android ViewBinding

Android ViewBinding使用详解