前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android视图绑定ViewBinding的使用

Android视图绑定ViewBinding的使用

作者头像
SoullessCoder
发布2021-09-07 16:54:02
2.4K0
发布2021-09-07 16:54:02
举报
文章被收录于专栏:CodeCode

前言

后台读者留言:能否写一篇视图绑定ViewBinding相关的内容?

首先感谢这位读者的提议,让我抽出时间细看视图绑定的内容,也打算在项目中使用该功能。当然,还有其他读者提议的内容我已记录,后期有时间也会陆续更新。话不多说,我们开始学习吧!

概述

在我们的开发过程中,需要获取XML布局文件中的ViewId,以便其赋值显示,我们习惯使用findViewById进行操作,可这样会导致很多的模版代码出现。直到Android大神 Jake Wharton开源了Butter Knife框架,通过Bind方式绑定获取ViewId。近两年谷歌对Kotlin的支持,我们开始使用 Android Kotlin extensions。在文件中导入布局文件直接引用viewId。无需做其他额外操作,最为方便。

目前,谷歌在 Android Studio 3.6 Canary 11 及更高版本中加入了新的视图绑定方式ViewBinding。

注意:要使用ViewBinding功能,AndroidStudio至少要升级到3.6。

分析

本文主要从以下方面对ViewBinding进行分析:

  • 使用能解决什么问题;
  • 使用流程;
  • 与之前方法的比较;
  • 原理;

1.使用能解决什么问题

顾名思义ViewBinding的意思就是如何将view与代码绑定在一起。所以其主要解决如何安全优雅地从代码中引用到XML layout文件中的view控件的问题。直到目前为止,Android构建用户界面的主流方式仍然是使用XML格式的layout文件。

2.使用流程

  • 在要使用ViewBinding的 module 的gradle文件中开启ViewBinding
android {
    ……………
    viewBinding {
        enabled = true
    }
    ……………
}
  • 如果在使用的过程中开发者不想为某个布局文件生成binding类,则可以使用如下属性添加到布局的根视图中即可:
<androidx.constraintlayout.widget.ConstraintLayout
  …………
      tools:viewBindingIgnore="true" >
  …………
</androidx.constraintlayout.widget.ConstraintLayout>
  • 编译此module获得XML布局文件对应的绑定类

在gradle文件中开启ViewBinding功能后,编译器就会为此模块下的每个布局文件都产生一个对应的绑定类。

假设我们有如下XML布局文件

<?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"
    tools:viewBindingIgnore="true" >

    <ImageView
        android:id="@+id/img_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher_background"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:id="@+id/tv_content"
        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" />

</androidx.constraintlayout.widget.ConstraintLayout>

这个类名称的命名规则为:XML布局文件名去掉下划线,下划线首字母大写,最后加上Binding。例如我有一个布局文件activity_main.xml,那对应生成的类文件为ActivityMainBinding.java。

生成的类文件位于Module路径: build\generated\data_binding_base_class_source_out\debug\out\包名\databinding下。

如下图所示:

在这里插入图片描述

  • 使用此生成类引用XML布局文件中的控件

调用生成类ActivityDescriptionBinding的inflate()方法获得类实例对象,通过getRoot()方法可以获得layout文件的最外层View,此例中是一个ConstraintLayout. 通过Activity的 setContentView()方法可以为Activity设置内容。layout文件中只要是有id的view, 在这个生成类中都会对应的生成一个 public final 的属性,例如:

<TextView
        android:id="@+id/tv_content"
        ...
        />

对应的生成字段为:

 @NonNull
 public final TextView tvContent;

那就可以直接使用对象实例访问了,如下代码所示:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置布局文件
        activityMainBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(activityMainBinding.getRoot());

        //设置文本
        activityMainBinding.tvContent.setText("要么停止成长,要么不断向前");
        //设置图片
        activityMainBinding.imgShow.setImageResource(R.drawable.img);
    }
}

3.与之前方法的比较

目前Android开发中完成View映射的方法主要有 findViewById、 ButterKnife, 如果使用kotlin的话还可以使用Kotlin Android Extensions。

这些方式的各方面对比如下:

在这里插入图片描述

ViewBinding对比以上方法有如下几点优势:

  • Type safety:findViewById, ButterKnife 均存在类型转换问题,例如不小心将一个TextView错误的赋值给一个Button变量,都会报错,这一错误很容易出现,关键在错误还出现在运行时,而不是编译时! 而ViewBinding中,产生的binding类中的属性是依据XML layout文件生成的,所以类型不会错,生成的时候已经处理好了。
  • Null safety: findViewById, ButterKnife与Kotlin Android Extensions 均存在Null不安全问题。这个什么意思呢?就是在我们访问那个View的时候它不存在。为什么会出现这种情况呢?例如不小心使用了错误的Id,或者访问的时候那个view还不存在。

使用了错误Id这个估计大家都有此类经历,但是访问时候那个view不存在怎么理解呢?例如我们在手机横屏和竖屏的时候分别使用一套XML layout文件,假设横屏中包含了一个竖屏中没有的view,那么在屏幕从横屏旋转到竖屏的时候,NullPointer问题就出现了。

而ViewBinding中, 产生的binding类中的属性是依据XML layout文件生成的,所以Id不会错。而且其将仅存在某一个配置下的layout文件的那些view对应的字段标记为@Nullable ,例如本例中的:

@NonNull
 public final TextView tvContent;

而且,生成类中还会很贴心的给你加上详细的注释。这一切都是为了提醒程序员,注意对这个view特别处理,它在某些情况下为Null。

  • 简洁优雅: 将绑定view的模板代码自动生成到了其他类中,使controlor类(Activity,Fragment)更加清晰了。

4.原理

通过上面分析,估计你对其原理也猜的的八九不离十了。就是Google在那个用来编译的gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。那些你所熟悉的findViewById操作都是在这个自动生成的类里面呢,如下所示:

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;

  @NonNull
  public final ImageView imgShow;

  @NonNull
  public final TextView tvContent;

  private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull ImageView imgShow,
      @NonNull TextView tvContent) {
    this.rootView = rootView;
    this.imgShow = imgShow;
    this.tvContent = tvContent;
  }

  @Override
  @NonNull
  public ConstraintLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding 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: {
      ImageView imgShow = rootView.findViewById(R.id.img_show);
      if (imgShow == null) {
        missingId = "imgShow";
        break missingId;
      }
      TextView tvContent = rootView.findViewById(R.id.tv_content);
      if (tvContent == null) {
        missingId = "tvContent";
        break missingId;
      }
      return new ActivityMainBinding((ConstraintLayout) rootView, imgShow, tvContent);
    }
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

其中核心代码是bind(@NonNull View rootView)方法,除此之外还有两个inflate()重载方法,一般情况下我们使用这两个方法获得binding类的实例,这些方法都是public static的,通过bind(@NonNull View rootView)这个方法应该可以实现延迟绑定,但是其使用场景应该很少。

总结

目前ViewBinding的功能还不够完善,比如XML中使用了 inClude 标签时无法对view进行引用。但总体来说已经很不错了。相比较于 findViewById 和 Butter Knife两种方式还是方便很多的。而且 ViewBinding 在使用的过程中不存在类型转换以及空指针异常的问题。因为在绑定类中已经全部定义好了,开发者直接使用就可以。


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-2-4 11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档