前言
2025年11月25日更新
JetBrains 和 Google 已于 2020 年宣布废弃 Kotlin Android Extensions(包括 synthetic),并在 Kotlin 1.8.0(2023 年初)中彻底移除支持。
所以现在只推荐使用ViewBinding或者原始方式。
常见获取方式:
原始方式
在我们的开发过程中,需要获取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 扩展已经不能用了,开发工具会报错。
搜索Kotlin扩展页面
搜索项目中所有使用Kotlin扩展的页面
搜索
1
| kotlinx.android.synthetic.main
|
如果没有我们项目就可以移除synthetic。
ViewBinding说明
原理就是
Google在gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。
开启ViewBinding
注意
只要开启后,会自动遍历layout下的xml文件自动生成对应的类。
版本对应关系
| Gradle 版本 |
Android Gradle Plugin (AGP) |
| 8.4 |
8.3.0 |
| 8.2 |
8.2.0 |
| 8.0 |
8.1.0 |
| 7.5 |
7.4.2 |
| 7.4 |
7.3.1 |
| 6.1.1 |
4.0.2 |
新版本
从 Android Gradle Plugin 4.0.0 开始,ViewBinding 被正式支持,开启方式如下:
配置build.gradle
1 2 3 4 5
| android { buildFeatures { viewBinding true } }
|
旧版本
在 AGP 3.6 到 3.9 之间,ViewBinding 是作为实验性功能引入的,需要通过以下方式开启:
配置build.gradle
1 2 3 4 5
| android { viewBinding{ enable = true } }
|
使用
Activity中使用
原来的
1
| setContentView(R.layout.activity_tex)
|
替换为
1 2 3 4
| private lateinit var binding: ActivityTexBinding
binding = ActivityTexBinding.inflate(layoutInflater) setContentView(binding.root)
|
使用
1 2
| val text = "$$ c = \\pm\\sqrt{a^2 + b^2} $$" binding.katexText.setText(text)
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import cn.psvmc.texeditor.databinding.ActivityTexBinding
class TexActivity : AppCompatActivity() { private lateinit var binding: ActivityTexBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityTexBinding.inflate(layoutInflater) setContentView(binding.root) val text = "$$ c = \\pm\\sqrt{a^2 + b^2} $$" binding.katexText.setText(text) } }
|
注意
ActivityTexBinding是自动生成的类,它会自动遍历layout下的xml文件自动生成对应的类。
比如我的XML是activity_tex.xml,它自动生成的类就是ActivityTexBinding。
如果想在生成绑定类时忽略某个布局文件,将tools:viewBindingIgnore=”true”`属性添加到相应布局文件的根视图中。
Fragment中使用
fragment_pdf2.xml生成的文件是FragmentPdf2Binding
示例:
Kotlin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class PdfFragment2(private val resource: YuxiResources) : Fragment() {
private var binding: FragmentPdf2Binding? = null
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
}
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = FragmentPdf2Binding.inflate(inflater, container, false); return binding?.getRoot(); }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding?.pdfView?.text = resource.pdfurl } }
|
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class MyFragment extends Fragment { private FragmentMyBinding binding;
public MyFragment() {
}
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentMyBinding.inflate(inflater, container, false); return binding.getRoot(); }
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); binding.textView.setText("这是Fragment"); binding.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d("Fragment", "点击了按钮"); } }); }
@Override public void onDestroy() { super.onDestroy(); binding = null; } }
|
Dialog中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class MyDialog extends Dialog { protected View mView; protected MyDialogBinding mBinding;
public MyDialog(@NonNull Context context) { super(context,R.style.Dialog); mBinding = MyDialogBinding.inflate(getLayoutInflater()); mView = mBinding.getRoot(); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(mView); Window window = this.getWindow(); WindowManager.LayoutParams lp = window.getAttributes(); Display d = window.getWindowManager().getDefaultDisplay(); lp.width = (int) (d.getWidth() * 0.9F); window.setAttributes(lp); }
}
|
Adapter 中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
private List<String> mList;
public MainAdapter(List<String> list) { mList = list; }
@NonNull @Override public MainAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutCommentBinding commentBinding = LayoutCommentBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); ViewHolder holder = new ViewHolder(commentBinding); return holder; }
@Override public void onBindViewHolder(@NonNull MainAdapter.ViewHolder holder, int position) { holder.mTextView.setText(mList.get(position)); }
@Override public int getItemCount() { return mList.size(); }
static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
ViewHolder(@NonNull LayoutCommentBinding commentBinding) { super(commentBinding.getRoot()); mTextView = commentBinding.tvInclude; }
} }
|
自定义View中使用
如果我们的自定义View中使用了layout布局,比如layout_my_view.xml,如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="这是自定义布局" android:textSize="50sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
|
会生成一个名为LayoutMyViewViewBinding.java文件,在自定义View通过如下方式绑定,
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyView extends View { public MyView (Context context) { this(context, null); }
public MyView (Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); }
public MyView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(getContext()), this, true); } }
|
如果自定义View布局文件中使用merge标签,
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="这是自定义merge" android:textSize="50sp" />
</merge>
|
此时要写成下面这个样子,
1
| LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(context), this);
|
include标签的使用
不带 merge 标签
include 标签不带 merge 标签,需要给 include 标签添加 id, 直接使用 id 即可,用法如下所示。
1 2 3
| <include android:id="@+id/include_title" layout="@layout/public_title" />
|
代码
1 2 3
| ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
binding.includeTitle.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge");
|
注意
原来include的组件上可以直接添加事件,改为ViewBinding后必须找到子View再添加事件
比如
1 2 3 4 5 6
| include_title .setOnTouchListener { p0, p1 -> hideInput(); true; }
|
得改为
1 2 3 4 5 6
| binding.includeTitle.rlytBaseKey .setOnTouchListener { p0, p1 -> hideInput(); true; }
|
带 merge 标签
include 标签带 merge 标签,需要通过bind()将merge布局绑定到主布局上,用法如下所示。
1 2
| <include layout="@layout/layout_merge_item" />
|
代码
1 2 3
| ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater); LayoutMergeItemBinding mergeItemBinding = LayoutMergeItemBinding.bind(binding.getRoot()); mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge");
|
迁移注意项
命名转换
XML 中的 progress_fl_main 用 binding.progress_fl_main 是找不到的,需要使用binding.progressFlMain
include
假如include的XML如下
1 2 3 4
| <include android:id="@+id/include_class" layout="@layout/public_key" android:visibility="gone" />
|
原来的
1
| include_class.visibility = View.GONE
|
要改为
1
| binding.includeClass.root.visibility = View.GONE
|
监听事件也要做类似的更改
1 2 3 4 5
| binding.includeClass.root.setOnTouchListener { p0, p1 -> hideInput(); true; }
|
注意
.root 是 布局绑定类的核心属性,代表你通过 <include> 标签引入的子布局的根视图(Root View)。
基类
Activity
1 2 3 4 5 6 7 8 9 10 11
| abstract class BaseActivityVB<T : ViewBinding> : AppCompatActivity() { private lateinit var _binding: T protected val binding get() = _binding protected abstract fun getViewBinding(): T override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = getViewBinding() setContentView(_binding.root) } }
|
使用
1 2 3 4 5 6 7 8 9 10
| class MainActivity : BaseActivityVB<ActivityMainBinding>() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding.textView.text = "这是MainActivity" }
override fun getViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) } }
|
如果有多个泛型
Kotlin
1 2 3
| abstract class BaseMvpActivityVB<T : BasePresenter<*>, U : ViewBinding> : BaseActivityVB<U>(), BaseView {
}
|
Java
1 2 3 4
| public abstract class BaseMvpActivityVB<T extends BasePresenter,U extends ViewBinding> extends BaseActivityVB<U> implements BaseView { }
|
Fragment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import androidx.viewbinding.ViewBinding
abstract class BaseFragmentVB<T : ViewBinding> : Fragment() { private lateinit var _binding: T protected val binding get() = _binding protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): T override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = getViewBinding(inflater, container) return _binding.root } }
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| class FirstFragment : BaseFragmentVB<FragmentFirstBinding>() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.textView.text = "这是FirstFragment" }
override fun getViewBinding( inflater: LayoutInflater, container: ViewGroup? ): FragmentFirstBinding { return FragmentFirstBinding.inflate(inflater, container, false) } }
|
如果有多个泛型
Kotlin
1 2 3 4
| abstract class BaseMvpFragmentVB<T : BasePresenter<*>?, U : ViewBinding> : BaseFragmentVB<U>(), BaseView {
}
|
Java
1 2 3 4
| public abstract class BaseMvpFragmentVB<T extends BasePresenter, U extends ViewBinding> extends BaseFragmentVB<U> implements BaseView { }
|
移除synthetic
迁移到 ViewBinding 后,可以完全移除 synthetic
在模块的 build.gradle 中 移除:
1
| id 'kotlin-android-extensions'
|
或
1
| apply plugin: 'kotlin-android-extensions'
|