赞
踩
首先,这是官方的文档讲解,很不错官方文档 【点击即可】
目录
随着Android Studio 3.6的正式发布,我义无反顾的走在了更新尝鲜的前列。AS的升级一如往常的顺利,重启后就进入了令人血脉喷张的 Gradle 升级的环节,需要从3.5.1升级到3.6.0。果不其然,出问题了!!
ButterKnife居然报错,日志如下:
- D:\xxx\libbase\component\dialog\BottomDialog.java:33: : Attempt to use @BindView for an already bound ID 0 on 'mTvNegative'. (com.xxx.libbase.component.dialog.BottomDialog.mLayoutContent)
- ViewGroup mLayoutContent;
我真是摸不着头脑啊。解决吧,升级ButterKnife、翻资料、找issue、看源码等等等等。最终老天不负有心人,我将Gradle版本回退了,一切都回归平静。【如果有解决办法的请告知我,感激不尽】
它和ButterKnife一样都是为了省去findViewById()这样的重复代码。其实在2019谷歌开发者峰会上对ViewBinding就已经有所耳闻了,layout中更新控件ID后立刻可以在Activity中引用到,这绝对比ButterKnife需要编译、需要区分R和R2要舒服的多。
上面升级到3.6.0就是为了使用它,然而现实永远这么的残酷,十之八九不尽人意,ViewBinding和ButterKnife看来只能二选一了。
关于ViewBinding的文档,官方写的很详细,请看 视图绑定 。本文一切从简,主要说下Google官方没有提到的一些问题。
ViewBinding支持按模块启用,在模块的build.gradle文件中添加如下代码:
- android {
- ...
- viewBinding {
- enabled = true
- }
- }
- //之前设置视图的方法
- setContentView(R.layout.activity_main);
-
- //使用ViewBinding后的方法
- mBinding = ActivityMainBinding.inflate(getLayoutInflater());
- setContentView(mBinding.getRoot());
可以看到,当你使用了ViewBinding后,针对你的activity_main.xml文件,会自动帮你生成一个ActivityMainBinding.java文件(该文件在build/generated/data_binding_base_class_source_out/xxx…目录下),也就是布局文件的驼峰命名法加上一个Binding后缀,然后在Activity中直接使用就可以。
当我们在布局中添加一个id为 tv_text 的TextView后,直接在Activity中使用mBinding.tvText即可拿到该控件。如下所示,可以看到也是以控件ID的驼峰命名法来获取的:
mBinding.tvText.setText("是你得不到的ViewBinding");
例如我们有个layout_comment.xml的布局,布局中有id为tv_include的TextView,代码如下:
- <?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:id="@+id/tv_include"
- android:text="这就是测试啊"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
然后在activity_main.xml文件中include该布局:
- <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/tv_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <include
- android:id="@+id/layout_include"
- layout="@layout/layout_comment" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>

那么此时我们如何使用到layout_comment.xml布局中的TextView控件呢,首先include标签需要声明id,例如layout_include,然后Activity中代码如下:
mBinding.layoutInclude.tvInclude.setText("这就是你的不对了");
是不是很神奇,是不是很简单。
注意:
当你给layout_comment.xml的根布局再添加id(比如添加了layout_xxx的ID)的时候,此时会报错:
java.lang.NullPointerException: Missing required view with ID: layout_xxx
我们将上文的layout_comment.xml稍作修改,根布局使用merge标签,其他不做修改:
- <?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="wrap_content">
-
- <TextView
- android:id="@+id/tv_include"
- android:text="这就是测试啊"
- android:gravity="end"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </merge>
- <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <include
- android:id="@+id/layout_include"
- layout="@layout/layout_comment" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml文件中使用include添加该布局后,在java代码中依旧是可以正常使用以下代码的:
mBinding.layoutInclude.tvInclude.setText("会不会出现问题呀");
但是但是!!!运行就会报错:
java.lang.NullPointerException: Missing required view with ID: layoutInclude
要是把include标签的id去掉的话,这时mBinding中也是找不到tvInclude这个控件呀,怎么办??
之前是不是说过,每个layout文件都会对应一个Binding文件,那么layout_comment.xml,肯定也有一个LayoutCommentBinding.java文件,我们去看下这个文件的源代码,里面有个可疑的方法,bind()方法:
- @NonNull
- public static LayoutCommentBinding bind(@NonNull View rootView) {
- // The body of this method is generated in a way you would not otherwise write.
- // This is done to optimize the compiled bytecode for size and performance.
- String missingId;
- missingId: {
- TextView tvInclude = rootView.findViewById(R.id.tv_include);
- if (tvInclude == null) {
- missingId = "tvInclude";
- break missingId;
- }
- return new LayoutCommentBinding(rootView, tvInclude);
- }
- throw new NullPointerException("Missing required view with ID: ".concat(missingId));
- }
所以对于含有merge标签的布局我们可以使用bind()方法来绑定到根布局上,在这里,根布局就是mBinding.getRoot()了。所以代码如下:
- //这么写不可以
- //mBinding.layoutInclude.tvInclude.setText("会不会出现问题呀");
-
- LayoutCommentBinding commentBinding = LayoutCommentBinding.bind(mBinding.getRoot());
- commentBinding.tvInclude.setText("这就不会出现问题了吧");
同时需要注意: include标签不可以有id
在Fragment的**onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)**方法中:
- //原来的写法
- return inflater.inflate(R.layout.fragment_blank, container, false);
-
- //使用ViewBinding的写法
- mBinding = FragmentBlankBinding.inflate(inflater);
- return mBinding.getRoot();
拿到FragmentBlankBinding的对象后,更新数据的都和之前一样了。
这里直接贴出来代码吧,就是自定义了一个Fragment然后往其中添加了一个布局,该布局是上文的layout_comment.xml文件,会生成一个对应的LayoutCommentBinding.java文件,同样可以直接使用:
- public class MyView extends FrameLayout {
-
- LayoutCommentBinding commentBinding;
-
- public MyView(@NonNull Context context) {
- this(context, null);
- }
-
- public MyView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initLayout();
- }
-
- private void initLayout() {
- //之前的写法
- //View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_comment, this, false);
- //addView(view);
-
- //使用ViewBinding的写法
- commentBinding = LayoutCommentBinding.inflate(LayoutInflater.from(getContext()), this, false);
- addView(commentBinding.getRoot());
- }
-
- public void setText(String str) {
- commentBinding.tvInclude.setText(str);
- }
- }

在RecyclerView结合Adapter的例子中我们再使用ViewBinding来尝试下,直接贴Adapter的代码:
- 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;
- }
-
- }
- }

只需要注意两方面:
使用ViewBinding的话,其实很简单,新建xxx.xml布局后就会产生一个对应的 xxxBinding.java的文件,实例化xxxBinding只需要调用它自身的inflate()方法即可。
注意不同情况下使用不同的inflate()方法,以及使用了merge标签情况下的bind()方法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。