是时候拥抱ViewBinding了~

是时候拥抱ViewBinding了~是时候拥抱 ViewBinding 了 一 前言二 初识 ViewBinding 三 拥抱 ViewBinding3 1 环境要求 3 2 开启 ViewBinding 功能 3 3 Activity 中 ViewBinding 的使用 3 3 1 布局中直接的控件 3 3 2 布局中导入的控件沉舟侧畔千帆过 病树前头万木春 唐 刘禹锡一 前言随着 AndroidStudi 6 的正式发布 我义无反

一、前言

随着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

三、拥抱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,代码如下:

 
    <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该布局:

 
    <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标签,其他不做修改:

 
    <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> 
 
    <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 
 @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的对象后,更新数据的都和之前一样了。

请注意官方的提示( 务必在onDestroyView() 方法中清除对绑定类实例的所有引用),感谢 @lingHui_1314 的反馈:

注意:Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView() 方法中清除对绑定类实例的所有引用。

3.5、自定义Dialog中使用ViewBinding

dialog中使用和Activity以及Fragment一样,直接使用单参数的inflate()方法即可,伪代码如下:

public class MyDialog extends Dialog { 
    protected View mView; protected DialogBottomBinding mBinding; public MyDialog(@NonNull Context context, @StyleRes int themeResId) { 
    super(context, themeResId); //原来的写法 mView = View.inflate(getContext(), getLayoutId(), null); //使用ViewBinding的写法 mBinding = DialogBottomBinding.inflate(getLayoutInflater()); mView = mBinding.getRoot(); setContentView(mView); } } 

3.6、自定义View中使用ViewBinding

我在重构工程的时候发现了自定义视图中其实有很多问题,这里把这两种常见的方法总结下:

3.6.1 使用的layout文件不包含merge

这里直接贴出来代码吧,就是自定义了一个LinearLayout然后往其中添加了一个布局,该布局是view_my_layout.xml文件,代码如下:

 
    <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> 
 public class MyLinearLayout extends LinearLayout { 
    public MyLinearLayout(Context context) { 
    this(context, null); } public MyLinearLayout(Context context, @Nullable AttributeSet attrs) { 
    this(context, attrs, 0); } public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 
    super(context, attrs, defStyleAttr); // init1(); // init2(); // init3(); init4(); } private void init1() { 
    inflate(getContext(), R.layout.view_my_layout, this); } private void init2() { 
    View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this); } //和init2()方法相等 private void init3() { 
    View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this, true); } private void init4() { 
    View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this, false); addView(view); } //视图异常,布局无法填充满 private void init10() { 
    ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext())); addView(binding.getRoot()); } private void init11() { 
    ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()), this, true); } private void init12() { 
    ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()), this, false); addView(binding.getRoot()); } } 

3.6.2 使用的layout文件根标签为merge

我们添加一个view_my_layout_merge.xml文件,根标签为merge:

 
    <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> 

此时在MyLinearLayout.java中使用的话,正确写法是init20()方法:

 private void init20() { 
    ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.inflate(LayoutInflater.from(getContext()), this); } //没有效果,可以理解为还没有rootView private void init21() { 
    ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.bind(this); } 
 @NonNull public static ViewMyLayoutMergeBinding inflate(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { 
    if (parent == null) { 
    throw new NullPointerException("parent"); } inflater.inflate(R.layout.view_my_layout_merge, parent); return bind(parent); } @NonNull public static ViewMyLayoutMergeBinding bind(@NonNull View rootView) { 
    if (rootView == null) { 
    throw new NullPointerException("rootView"); } return new ViewMyLayoutMergeBinding(rootView); } 

不使用merge标签的Binding代码如下,inflate(@NonNull LayoutInflater inflater) 调用了 inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) 方法,最终调用了bind(@NonNull View rootView)方法:

 @NonNull public static ViewMyLayoutBinding inflate(@NonNull LayoutInflater inflater) { 
    return inflate(inflater, null, false); } @NonNull public static ViewMyLayoutBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { 
    View root = inflater.inflate(R.layout.view_my_layout, parent, false); if (attachToParent) { 
    parent.addView(root); } return bind(root); } @NonNull public static ViewMyLayoutBinding bind(@NonNull View rootView) { 
    if (rootView == null) { 
    throw new NullPointerException("rootView"); } return new ViewMyLayoutBinding((ConstraintLayout) rootView); } 

3.7、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对象

四、关于封装

五、总结

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212852.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午7:06
下一篇 2026年3月18日 下午7:07


相关推荐

  • 自定义控件_绘制太极(拖动)

    自定义控件_绘制太极(拖动)packagecom.example.administrator.houzengyu_0417;importandroid.content.Context;importandroid.graphics.Canvas;importandroid.graphics.Color;importandroid.graphics.Paint;importandroid.graphics

    2022年6月3日
    26
  • 如何搭建自己的IOS分发平台

    如何搭建自己的IOS分发平台1 将签名后的 ipa 文件部署到服务器 1 首先这个网站要支持 HTTPS 协议 用来访问下载 plist 文件 我们可以自己申请证书来配置 2 除了 ipa plist 这两个文件 我们还需要提供两个图片 就是配置 plist 信息的时候填写的 一个尺寸是 57X57 像素 用来显示下载和安装过程中的图标 一个尺寸是 512X512 像素 用来在 iTunes 中显示 3 同时

    2026年3月16日
    2
  • 关于VS中的sln文件[通俗易懂]

    关于VS中的sln文件[通俗易懂]sln文件我们在应用VC6以前,发现的是dsp和.dsw文件,打开dsw文件可以打开整个工程VS.net2002以后是.vcproj和.sln.sln对应以前的.dsw.vcproj对应以前的.dspvcproj是工程文件,sln是解决方案文件。一个解决方案里面可以包含多个工程。打开vcproj文件编译不了可能是因为这个工程里面用到了同一个解决方案中其它工程的东西。Vi

    2022年4月30日
    69
  • 魔兽争霸微操教学(精华篇)「建议收藏」

    魔兽争霸微操教学(精华篇)「建议收藏」基础操作:用键盘制造单位,释放魔法,使用物品。魔兽中的一切东西都可以用快捷键来完成,而鼠标只是起到一个定位的作用。比如,暗夜做小精灵,你可以用鼠标点击基地里精灵的头像,也可以直接按w;或者暗夜做月亮

    2022年7月1日
    33
  • Wing Pro 11 的新增功能 – Python IDE

    Wing Pro 11 的新增功能 – Python IDE

    2026年3月15日
    3
  • 用户案例怎么写_用户运营案例

    用户案例怎么写_用户运营案例用户案例 – 3Cs

    2022年4月21日
    52

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号