是时候拥抱ViewBinding了!!

  • 一、前言
  • 二、初识ViewBinding
  • 三、拥抱ViewBinding
    • 3.1、环境要求
    • 3.2、开启ViewBinding功能
    • 3.3、Activity中ViewBinding的使用
      • 3.3.1、布局中直接的控件
      • 3.3.2、布局中使用include
      • 3.3.2、布局中使用include和merge
    • 3.4、Fragment中使用ViewBinding
    • 3.5、自定义Dialog中使用ViewBinding
    • 3.6、自定义View中使用ViewBinding
      • 3.6.1 使用的layout文件不包含merge
      • 3.6.2 使用的layout文件根标签为merge
    • 3.7、Adapter中使用ViewBinding
  • 四、关于封装
  • 五、总结

沉舟侧畔千帆过,
病树前头万木春。
– 唐·刘禹锡

一、前言

随着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"><TextViewandroid: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"><TextViewandroid: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" /><includeandroid: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"><TextViewandroid: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"><includeandroid: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()方法:

 @NonNullpublic 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文件,代码如下:

<?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"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="这是自定义布局"android:textSize="50sp" /></androidx.constraintlayout.widget.ConstraintLayout>

会生成一个对应的ViewMyLayoutBinding.java文件,看下文MyLinearLayout 代码:
init1、2、3、4是使用inflate来导入layout布局的写法,全部可以正常显示自定义的布局。
init10、11、12是使用ViewBinding的写法,10无法正常显示视图,11和12是两种不同的写法,道理一样。


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:

<?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"><TextViewandroid: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);}//没有效果,可以理解为还没有rootViewprivate void init21() {ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.bind(this);}

我们对比下使用merge标签和不使用merge标签所对应的Binding文件:
使用merge标签生成的代码大致如下,inflate()方法最终调用了bind()方法:

  @NonNullpublic 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);}@NonNullpublic 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)**方法:

  @NonNullpublic static ViewMyLayoutBinding inflate(@NonNull LayoutInflater inflater) {return inflate(inflater, null, false);}@NonNullpublic 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);}@NonNullpublic static ViewMyLayoutBinding bind(@NonNull View rootView) {if (rootView == null) {throw new NullPointerException("rootView");}return new ViewMyLayoutBinding((ConstraintLayout) rootView);}

这里基本就把所有的自定义视图中使用ViewBinding的方法总结了一下,主要是inflate方法的使用,其实就是帮我们封装了下inflate方法,如果不知道使用哪个方法的话可以查看生成的ViewBinding源代码,一眼就能明了我们之前的写法对应的是现在的哪个方法了。
如果还不熟悉的话,请翻阅其他inflate的相关资料,相信你会有很大收货。当然了当你熟悉inflate方法之后,下面的文章其实可以没必要看了。

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@Overridepublic 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;}@Overridepublic void onBindViewHolder(@NonNull MainAdapter.ViewHolder holder, int position) {holder.mTextView.setText(mList.get(position));}@Overridepublic 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后,我们可以考虑将其完全封装在BaseActivity(BaseFragment、BaseDialog、BaseView等)等底层的公共类中,省去手动实例化相应ViewBinding类的这一过程。
首先可以使用泛型类,每个具体的Activity继承BaseActivity,并传递进去对应的ViewBinding,然后反射对应的inflate()方法获取到ViewBinding实例。拿到实例后就可以对控件为所欲为了不是!!

五、总结

使用ViewBinding的话,其实很简单,新建xxx.xml布局后就会产生一个对应的 xxxBinding.java的文件,实例化xxxBinding只需要调用它自身的inflate()方法即可。
注意不同情况下使用不同的inflate()方法,以及使用了merge标签情况下的bind()方法,以及使用merge标签布局和其他正常xxxLayout布局所产生的不同的inflate()方法。

是时候拥抱ViewBinding了~相关推荐

  1. 海外直播、聊天交友APP的开发及上架GooglePlay体验【Compose版】

    前言 Jetpack Compose在2021年7月底的时候正式发布了Release 1.0版本,在8月中旬的时候正好赶上公司海外项目计划重构,于是主动请缨向领导申请下来了此次开发的机会.由于之前一直 ...

  2. 拥抱AI技术,赋能智慧工业

    伴随着科技的进步,人工智能.数字化技术的影子已延伸到各行各业,以AI技术为代表的智能化时代的到来,拉开了第四次工业革命的帷幕.制造业作为社会生产的基石,自然需要拥抱AI技术,助力高质量发展. 深度学习 ...

  3. 积极拥抱.NET Core开源社区

    潘正磊在上海的Tech Summit 2018 大会上给我们的.NET Core以及开源情况带来了最新信息. .Net Core 开源后取得了更加快速的发展,目前越活跃用户高达400万人,每月新增开发 ...

  4. 化敌为友 运营商组团拥抱OTT为哪般

    2月15日,全球9大电信运营商宣布,成立"合作运营联盟"(Partnering Operator Alliance),协力挖掘互联网企业为电信业带来的增长潜力.从几年前的抵御OTT ...

  5. 活动|跟着微软一起,拥抱开源吧!

    由开源社主办的中国开源年会2016 (COSCon'16 - China Open Source Conference 2016) 即将于今年10月15日-16日在北京举办.微软大咖将为您呈现区块链, ...

  6. IBM全面拥抱Linux,为“认知商业”提供POWER

    ZD至顶网服务器频道 03月03日 新闻消息(文/于泽):3月1日,IBM开年大会--IBM论坛2016盛大开启."认知商业"是IBM此次提出并覆盖未来一段时间的目标或者愿景,等同 ...

  7. 中国电子学会青少年编程能力等级测试图形化一级编程题:小鸡与鸭妈拥抱

    「青少年编程竞赛交流群」已成立(适合6至18周岁的青少年),公众号后台回复[Scratch]或[Python],即可进入.如果加入了之前的社群不需要重复加入. 我们将有关编程题目的教学视频已经发布到抖 ...

  8. 中国国际消费电子博览会拥抱转型,全新面貌拭目以待!

    2021年9月24-26日,第十九届中国国际消费电子博览会(简称电博会)将在青岛国际会展中心隆重举行,如今距离电博会开幕已不到3个月的时间,全国各地的参展企业跃跃欲试.积极筹备. 长久以来,电博会为全 ...

  9. 知乎 CTO 李大海:创业公司如何拥抱开源

    由计算机学会主办的 CCF C³ 活动近日走进知乎.知乎合伙人兼 CTO 李大海.搜狗 CEO 王小川.华东师范大学教授王伟在内的企业界.学界技术代表齐聚,带来一场精彩的技术分享盛宴. 围绕「创业公司 ...

  10. 腾讯拥抱开源:首次公布开源路线图,技术研发向共享、复用和开源迈进

    整理 | 夕颜 出品 | AI科技大本营(ID:rgznai100) 导读:去年,知乎上一篇讨论腾讯技术的帖子异常火爆,讨论的主题是当下(2018 年)腾讯的技术建设是否处于落后同体量公司的状态,这篇 ...

最新文章

  1. Xilinx SelectIO 接口
  2. 011 数据结构逆向—二叉树
  3. SandyMandy ,绝世好BABY http://angel.mingox.com
  4. 通过jsp向mysql批量导入数据_JSP+Servlet+C3P0+Mysql实现的图书馆管理系统
  5. Android 应用开发(42)---ImageView
  6. 写给初学者的话---linux使用说明
  7. RocksDB 6.0.1 发布,Facebook 推出的存储系统
  8. 分享小知识:善用Group By排序
  9. 航空订票系统php,C++版数据结构航空订票系统源代码.doc
  10. 研华工控台式计算机选型,工控机选型手册.pdf
  11. 商业原画与商业插画的区别
  12. numpy中axis理解
  13. <OS Concepts> 1- Intro
  14. 武汉最新测绘资质复审、换证所需资料及证书有效期
  15. 遇到vmware提示客户机操作系统已禁用 CPU.请关闭或重置虚拟机
  16. 其实能让我们感觉美好的只是回忆而已
  17. 找实习经历分享(一)
  18. Android Studio 亲感受之org.jdom.JDOMException: java.lang.ClassNotFoundException: org.jdom.xpath.JaxenXPa
  19. 设计模式--观察者模式(Observer)
  20. java与3d建模_java和3d游戏建模哪个?有没有3d建模转java或java转3的建模大佬给我指点一下...

热门文章

  1. 计算机理论什么是信道容量,(信道容量知识总结.doc
  2. 怎么把图片转换成ico格式
  3. 无法重命名文件夹,错误0x80004005 未指定的错误
  4. ASP.NET清除Cookies
  5. C# 类库组件在工具箱不显示问题
  6. html table自动序号,layui 给数据表格加序号的方法
  7. 在水晶报表中实现任意选择指定字段显示 (阿泰)
  8. 程序员根本不是稳定工作!
  9. educoder1-2Python 计算思维训练——公式计算
  10. Bigemap支持百度地图