1. 前言
- 如何干掉模版代码是很多第三方框架的设计初衷,在Android开发中,findViewById()是必不可少的存在,这样的冗余代码在很久以前充斥在Android工程中,因此也出现了很多精简方案。
- 在Android Studio3.6中加入了很多新特性——View Binding就是其中之一。目前已经在工程中使用并上线,未出现稳定性问题,因此做以下记录。
- 在谈View Binding之前,我们先聊一下在此之前有哪些代表性方案做过这个事情,以及它们的优缺点分析。
2. View Binding
这就是今天的主角了,View Binding既不会像Butter Knife那样为View分配多余的对象,也不会像Data Binding那样使用Annotation Processor,它的实现既简单又强大。首先来看一下如何使用View Binding。
2.1 View Binding所需环境
- 首先需要将Android Studio升级到3.6版本及以上
- 升级Gradle plugin版本到3.6.1
- 并在app模块中手动开启view binding开关(View Binding以模块为粒度进行开启/关闭)
buildscript { ... dependencies { classpath "com.android.tools.build:gradle:3.6.1" } } android { ... viewBinding { enabled = true } }
这里升级gradle的时候遇到了一些小坑,即工程中使用的tinker版本过低,其使用了旧版gradle中的语法,因此会报错,将tinker版本升级到’1.9.14.6’后问题解决。
2.2 View Binding的使用
activity_main.xml的布局如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/first_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="First Button" /> <include android:id="@+id/inner_layout" layout="@layout/inner_layout" /> </LinearLayout>
这里直接include了一个子布局,inner_layout.xml的布局如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/inner_layout_second_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Inner Button" /> </FrameLayout>
- 默认情况下,每一个布局xml文件都会生成一个对应的Binding类,名字的生成规则同Data Binding。
- 在该例子中,会自动为我们生成一个ActivityMainBinding.java,以及include的标签对应的InnerLayoutBinding,这里include必须指定一个id。
- 当然,如果不需要为该xml生成Binding类,可以在xml的根布局中配置tools:viewBindingIgnore=“true”。
在Java中的使用如下所示:
public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(LayoutInflater.from(this)); setContentView(binding.getRoot()); binding.firstButton.setText("Text Change"); binding.innerLayout.innerLayoutSecondButton.setText("Inner Layout Button Text Change"); } }
用法也很简单,直接通过binding点名字的方式就可以获取到控件实例,消除了findViewById的模版代码。
这里可以看到,setContentView的入参写法都变了,因为可通过XXXBinding类的getRoot函数获取到布局的根View,再通过setContentView添加到Activity。
2.3 View Binding的一些注意点
在2.2中我们演示了include的用法,只需要加一个id,我们就可以通过binding.innerLayout获取到该子布局。但是有个特殊情况需要处理,就是merge标签。
如果子布局inner_layout.xml中的布局如下,多了一个merge标签:
<?xml version="1.0" encoding="utf-8"?> <merge> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/inner_layout_second_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Inner Button" /> </FrameLayout> </merge>
那么在activity_main.xml的布局的include标签中就一定不要设置id了(这里就不贴activity_main.xml的代码了),否则会找不到View报空指针异常。这个情况,我们可以先初始化主布局,再初始带merge的布局,那么就需要用到View Binding的另一个构造方法了,处理如下:
public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(LayoutInflater.from(this)); setContentView(binding.getRoot()); //使用 binding.firstButton.setText("Text Change"); //merge标签处理 InnerLayoutBinding subBinding = InnerLayoutBinding.bind(binding.getRoot()); subBinding.innerLayoutSecondButton.setText("Inner Layout Button Text Change"); } }