前言
原始方式
在我们的开发过程中,需要获取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扩展
更好。 它是官方推荐的方式,并且可以避免一些潜在的运行时异常。 但是,如果你已经熟悉并且喜欢使用Kotlin 扩展
,并且对 APK 大小没有严格要求,那么你可以选择使用它。
ViewBinding
原理就是
Google在那个用来编译的gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。
开启
KTS方式配置
build.gradle.kts中添加
代码语言:javascript复制android {
viewBinding{
enable = true
}
}
注意
只要开启后,会自动遍历layout下的xml文件自动生成对应的类。
使用
页面中使用
代码语言:javascript复制private lateinit var binding: ActivityTexBinding
原来的
代码语言:javascript复制setContentView(R.layout.activity_tex)
替换为
代码语言:javascript复制binding = ActivityTexBinding.inflate(layoutInflater)
setContentView(binding.root)
使用
代码语言:javascript复制val text = "$$ c = \pm\sqrt{a^2 b^2} $$"
binding.katexText.setText(text)
完整代码
代码语言:javascript复制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”`属性添加到相应布局文件的根视图中。
基类
Activity
代码语言:javascript复制abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
private lateinit var _binding: T
protected val binding get() = _binding;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = getViewBinding()
setContentView(_binding.root)
}
protected abstract fun getViewBinding(): T
}
使用
代码语言:javascript复制class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.textView.text = "这是MainActivity"
}
override fun getViewBinding() = ActivityMainBinding.inflate(layoutInflater)
}
Fragment
代码语言:javascript复制abstract class BaseFragment<T : ViewBinding> : Fragment() {
private lateinit var _binding: T
protected val binding get() = _binding;
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = getViewBinding(inflater, container)
return _binding.root
}
protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): T
}
使用
代码语言:javascript复制class FirstFragment : BaseFragment<FragmentFirstBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textView.text = "这是FirstFragment"
}
override fun getViewBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentFirstBinding.inflate(inflater, container, false)
}
其他
Fragment中使用
代码语言:javascript复制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中使用
代码语言:javascript复制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 中使用
代码语言:javascript复制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) {
//之前的写法
//View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_comment, parent, false);
//ViewHolder holder = new ViewHolder(view);
//使用ViewBinding的写法
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;
//之前的写法
//public ViewHolder(@NonNull View itemView) {
// super(itemView);
// mTextView = itemView.findViewById(R.id.tv_include);
//}
//使用ViewBinding的写法
ViewHolder(@NonNull LayoutCommentBinding commentBinding) {
super(commentBinding.getRoot());
mTextView = commentBinding.tvInclude;
}
}
}
自定义View中使用
如果我们的自定义View中使用了layout布局,比如layout_my_view.xml,如下
代码语言:javascript复制<?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通过如下方式绑定,
代码语言:javascript复制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标签,
代码语言:javascript复制<?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>
此时要写成下面这个样子,
代码语言:javascript复制LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(context), this);
include标签的使用
include 标签不带 merge 标签,需要给 include 标签添加 id, 直接使用 id 即可,用法如下所示。
代码语言:javascript复制<include
android:id="@ id/include"
layout="@layout/layout_include_item" />
代码
代码语言:javascript复制ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
binding.include.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge");
include 标签带 merge 标签,需要通过bind()将merge布局绑定到主布局上,用法如下所示。
代码语言:javascript复制<include
layout="@layout/layout_merge_item" />
代码
代码语言:javascript复制ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
LayoutMergeItemBinding mergeItemBinding = LayoutMergeItemBinding.bind(binding.getRoot());
mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge");