文章

ViewBinding绑定组件

ViewBinding的目的只有一个,就是为了避免编写findViewById,要想使用ViewBinding需要注意两件事,第一,确保你的AndroidStudio是3.6或更高的版本。

一、准备工作

在你项目工程模块的build.gradle中加入以下配置:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

这样准备工作就完成了,下面会从ActivityFragmentAdapter引入布局四个方面分别讨论ViewBinding的用法。

二、在Activity中使用ViewBinding

一旦启动了ViewBinding功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。

Binding类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding作为结尾,比如说activity_main.xml布局,那么与它对应的Binding类就是ActivityMainBinding。

1.不为指定布局生成对应Binding

<LinearLayout
    xmlns:tools="http://schemas.android.com/tools"
    ...
    tools:viewBindingIgnore="true">
    ...
</LinearLayout>

2.在Activity中使用ViewBinding来设置TextView组件的text

class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.textView.text = "Hello"
    }
}

首先调用activity_main.xml布局文件对应的Binding类的inflate()函数去加载该布局,inflate()函数接受一个LayoutInflater参数,在Activity中是可以直接获取到的。调用Binding类的getRoot()函数可以得到activity_main.xml中根元素的实例,调用getTextView()函数可以获得id为textView的元素实例。然后将根元素实例传到setContentView()函数中,这样Activity就可以成功显示activity_main.xml这个布局的内容了。

三、在Fragment中使用ViewBinding

假设我们有一个布局文件叫fragment_main.xml,启用ViewBinding功能之后,会生成一个与其对应的FragmentMainBinding类。

class MainFragment : Fragment() {
    private var _binding: FragmentMainBinding? = null
    
    private val binding get() = _binding!!
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View {
       _binding = FragmentMainBinding.inflater(inflater, container, false)
       return binding.root
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

调用FragmentMainBinding的inflater()函数去加载fragment_main.xml布局文件,由于我们是在onCreateView()函数中加载的布局,那么理应在onDestroyView()函数中对binding变量置空,从而保证binding变量有效生命周期是在onCreateView()函数和onDestroyView()函数之间。

四、在Adapter中使用ViewBinding

假设我们定义了fruit_item.xml来作为RecyclerView子项的布局。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp">
    
    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />

</LinearLayout>

然后编写如下RecyclerView Adapter来加载和显示这个子项布局:

class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
    inner class ViewHolder(binding: FruitItemBinding) : RecyclerView.ViewHolder(binding.root) {
        val fruitImage: ImageView = binding.fruitImage
        val fruitName: TextView = binding.fruitName
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = FruitItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId)
        holder.fruitName.text = fruit.name
    }

    override fun getItemCount() = fruitList.size

}

首先,在onCreateViewHolder()函数中调用FruitItemBinding的inflate()函数去加载fruit_item.xml布局文件,这和ViewBinding在Fragment中的用法是一模一样的。

五、在引入布局中使用ViewBinding

1.在引入布局中使用ViewBinding——include

假设我们有如下titlebar.xml布局,希望作为通用布局引入到各布局当中:

<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_main.xml中引入布局并给include标签加上id:

<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>

在MainActivity中引用titlebar.xml中定义的组件:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

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

2.在引入布局中使用ViewBinding——merge

merge与include最大的区别在于,使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味着更高的效率。比如我们队titlebar.xml进行如下修改:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <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" />
</merge>

可以看到,这里最外层布局使用了merge标签,这就表示当有任何一个地方去include这个布局时,会将merge标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构。
但是很遗憾,如果依旧给include加上id用来绑定merge中的组件的话会导致程序的崩溃,所以应该去掉id。

<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/titlebar" />
</LinearLayout>

然后修改MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding
    private lateinit var titlebarBinding: TitlebarBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        titlebarBinding = TitlebarBinding.bind(binding.root)
        setContentView(binding.root)
        titlebarBinding.title.text = "Title"
        titlebarBinding.back.setOnClickListener {
        }
        titlebarBinding.done.setOnClickListener {
        }
    }

}
License:  CC BY 4.0