首先,这是官方的文档讲解,很不错官方文档 【点击即可】
目录
一、前言
随着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版本回退了,一切都回归平静。【如果有解决办法的请告知我,感激不尽】
二、初识ViewBinding
它和ButterKnife一样都是为了省去findViewById()这样的重复代码。其实在2019谷歌开发者峰会上对ViewBinding就已经有所耳闻了,layout中更新控件ID后立刻可以在Activity中引用到,这绝对比ButterKnife需要编译、需要区分R和R2要舒服的多。
上面升级到3.6.0就是为了使用它,然而现实永远这么的残酷,十之八九不尽人意,ViewBinding和ButterKnife看来只能二选一了。
三、拥抱ViewBinding
关于ViewBinding的文档,官方写的很详细,请看 视图绑定 。本文一切从简,主要说下Google官方没有提到的一些问题。
3.1、环境要求
- Android Studio版本3.6及以上
- Gradle 插件版本3.6.0及以上
3.2、开启ViewBinding功能
ViewBinding支持按模块启用,在模块的build.gradle文件中添加如下代码:
-
android {
-
...
-
viewBinding {
-
enabled =
true
-
}
-
}
3.3、Activity中ViewBinding的使用
-
//之前设置视图的方法
-
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中直接使用就可以。
3.3.1、布局中直接的控件
当我们在布局中添加一个id为 tv_text 的TextView后,直接在Activity中使用mBinding.tvText即可拿到该控件。如下所示,可以看到也是以控件ID的驼峰命名法来获取的:
mBinding.tvText.setText("是你得不到的ViewBinding");
3.3.2、布局中使用include
例如我们有个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
3.3.2、布局中使用include和merge
我们将上文的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
3.4、Fragment中使用ViewBinding
在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的对象后,更新数据的都和之前一样了。
3.5、自定义View中使用ViewBinding
这里直接贴出来代码吧,就是自定义了一个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);
-
}
-
}
3.6、Adapter中使用ViewBinding
在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;
-
}
-
-
}
-
}
只需要注意两方面:
- ViewHolder的构造器参数改为使用的Binding对象
- 实例化ViewHolder的时候传入相应的Binding对象
四、总结
使用ViewBinding的话,其实很简单,新建xxx.xml布局后就会产生一个对应的 xxxBinding.java的文件,实例化xxxBinding只需要调用它自身的inflate()方法即可。
注意不同情况下使用不同的inflate()方法,以及使用了merge标签情况下的bind()方法。