前言
原始方式
在我们的开发过程中,需要获取XML布局文件中的ViewId,以便其赋值显示,我们习惯使用findViewById进行操作,可这样会导致很多的模版代码出现。
Butter Knife框架
直到Android大神 Jake Wharton开源了Butter Knife框架,通过Bind方式绑定获取ViewId。
基于Kotlin的扩展
近几年Android对Kotlin的支持,我们开始使用 Android Kotlin extensions。
在文件中导入布局文件直接引用viewId。无需做其他额外操作,最为方便。
生成代码的方式
谷歌在 Android Studio 3.6 Canary 11 及更高版本中加入了新的视图绑定方式ViewBinding。
注意:
要使用ViewBinding功能,AndroidStudio至少要升级到3.6。
ViewBinding和Kotlin扩展
ViewBinding 和 Kotlin 扩展 都是 Android 开发中常用的技术,用于简化视图查找和绑定的过程。
以下是它们之间的一些比较:
ViewBinding:
ViewBinding是由 Android 官方推荐和支持的库,从 Android Studio 3.6 版本开始引入。ViewBinding使用了编译时生成的绑定类,在xml布局文件中的每个视图都会生成一个对应的绑定类对象,因此在编译时检测到视图名称的错误。ViewBinding可以生成类型安全的代码,避免了手动查找和强制转换视图对象的麻烦。ViewBinding不会增加 APK 大小,因为它只是编译时生成的代码。- 在多个模块中引用同一个视图时可能会出现命名冲突的问题,需要通过手动指定全限定名解决。
Kotlin扩展:
Kotlin扩展是 Kotlin 语言的特性,其通过扩展函数的方式,允许开发者为现有的类添加新的函数或属性。Kotlin扩展使用起来相对简单,可以直接在布局文件中使用 Kotlin 扩展函数来查找和操作视图。Kotlin扩展对视图的数据获取和类型安全性没有提供直接的支持,需要手动处理可能的空指针异常和类型转换。Kotlin扩展会增加 APK 的大小,因为它是在运行时动态添加的函数。
总体而言
ViewBinding在类型安全性和编译时错误检测方面比Kotlin扩展更好。它是官方推荐的方式,并且可以避免一些潜在的运行时异常。
(2025.09.10更新)现在只推荐
ViewBinding,Kotlin 扩展已经不能用了,开发工具会报错。
ViewBinding
原理就是
Google在那个用来编译的gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。
开启ViewBinding
KTS方式配置
build.gradle.kts中添加
1 | android { |
注意
只要开启后,会自动遍历layout下的xml文件自动生成对应的类。
使用
Activity中使用
原来的
1 | setContentView(R.layout.activity_tex) |
替换为
1 | private lateinit var binding: ActivityTexBinding |
使用
1 | val text = "$$ c = \\pm\\sqrt{a^2 + b^2} $$" |
完整代码
1 | import android.os.Bundle |
注意
ActivityTexBinding是自动生成的类,它会自动遍历layout下的xml文件自动生成对应的类。比如我的XML是
activity_tex.xml,它自动生成的类就是ActivityTexBinding。如果想在生成绑定类时忽略某个布局文件,将tools:viewBindingIgnore=”true”`属性添加到相应布局文件的根视图中。
Fragment中使用
fragment_pdf2.xml生成的文件是FragmentPdf2Binding
示例:
Kotlin
1 | class PdfFragment2(private val resource: YuxiResources) : Fragment() { |
Java
1 | public class MyFragment extends Fragment { |
Dialog中使用
1 | class MyDialog extends Dialog { |
Adapter 中使用
1 | public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> { |
自定义View中使用
如果我们的自定义View中使用了layout布局,比如layout_my_view.xml,如下
1 |
|
会生成一个名为LayoutMyViewViewBinding.java文件,在自定义View通过如下方式绑定,
1 | public class MyView extends View { |
如果自定义View布局文件中使用merge标签,
1 |
|
此时要写成下面这个样子,
1 | LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(context), this); |
include标签的使用
不带 merge 标签
include 标签不带 merge 标签,需要给 include 标签添加 id, 直接使用 id 即可,用法如下所示。
1 | <include |
代码
1 | ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater); |
注意
原来include的组件上可以直接添加事件,改为ViewBinding后必须找到子View再添加事件
比如
1 | include_title |
得改为
1 | binding.includeTitle.rlytBaseKey |
带 merge 标签
include 标签带 merge 标签,需要通过bind()将merge布局绑定到主布局上,用法如下所示。
1 | <include |
代码
1 | ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater); |
迁移注意项
命名转换
XML 中的 progress_fl_main 用 binding.progress_fl_main 是找不到的,需要使用binding.progressFlMain
include
假如include的XML如下
1 | <include |
原来的
1 | include_class.visibility = View.GONE |
要改为
1 | binding.includeClass.root.visibility = View.GONE |
监听事件也要做类似的更改
1 | binding.includeClass.root.setOnTouchListener { |
注意
.root是 布局绑定类的核心属性,代表你通过<include>标签引入的子布局的根视图(Root View)。
基类
Activity
1 | abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() { |
使用
1 | class MainActivity : BaseActivity<ActivityMainBinding>() { |
Fragment
1 | abstract class BaseFragment<T : ViewBinding> : Fragment() { |
使用
1 | class FirstFragment : BaseFragment<FragmentFirstBinding>() { |