Butter Knife框架是17年前后很火的存在。但是在Kotlin中直接使用ButterKnife的注解方式的话,会出现空指针的异常并导致绑定失败。从而Kotter Knife应运而生,可以理解成是Butter Knife的Kotlin版本。用法如下:
//Butter Knife
@BindView(R.id.title) TextView title;
ButterKnife.bind(this);
// TODO Use fields...
// Kotter Knife
val submitButton: Button by bindView(R.id.submit_button)
// TODO Use val...
然而这种方案已经被作者标记为Deprecated,作者认为该框架为每个视图引用分配了一个对象,这种思路不应该被采用,以及使用这种思路的框架也应该被淘汰,并推荐使用ViewBinding的方式。
依稀记得Data Binding是16年底的时候推出的,那时候真的很火,因为它是谷歌对于MVVM开发模式在Android上体现之一,其底层通过Annotation Processor实现的。用法如下,关键就是新增了data节点,以及在layout中的属性表达式@{}。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
LinearLayout>
layout>
默认情况下,基于layout文件的名称会生成单词首字母大写并添加“Binding”后缀一个Binding类。此类包含layout属性以及在Views中的所有binding属性(例如user变量),并且能够为该属性赋值。因此在Java代码中:
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("firstName", "lastName");
binding.setUser(user);
但是Data Binding的劣势也很明显:
这就是今天的主角了,View Binding既不会像Butter Knife那样为View分配多余的对象,也不会像Data Binding那样使用Annotation Processor,它的实现既简单又强大。首先来看一下如何使用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’后问题解决。
activity_main.xml的布局如下:
<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的布局如下:
<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>
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.2中我们演示了include的用法,只需要加一个id,我们就可以通过binding.innerLayout获取到该子布局。但是有个特殊情况需要处理,就是merge标签。
如果子布局inner_layout.xml中的布局如下,多了一个merge标签:
<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");
}
}
从2.2和2.3中我们看到了Binding类的两种构造方法,其实它有三种使用方法,如下所示:
inflate(@NonNull LayoutInflater inflater)
inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent)
bind(@NonNull View rootView)
// Generated by view binder compiler. Do not edit!
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
@NonNull
public final Button firstButton;
@NonNull
public final InnerLayoutBinding innerLayout;
private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull Button firstButton,
@NonNull InnerLayoutBinding innerLayout) {
this.rootView = rootView;
this.firstButton = firstButton;
this.innerLayout = innerLayout;
}
@Override
@NonNull
public LinearLayout 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) {
String missingId;
missingId: {
//绑定View且做了非空判断,否则抛出空指针
Button firstButton = rootView.findViewById(R.id.first_button);
if (firstButton == null) {
missingId = "firstButton";
break missingId;
}
View innerLayout = rootView.findViewById(R.id.inner_layout);
if (innerLayout == null) {
missingId = "innerLayout";
break missingId;
}
InnerLayoutBinding innerLayoutBinding = InnerLayoutBinding.bind(innerLayout);
return new ActivityMainBinding((LinearLayout) rootView, firstButton, innerLayoutBinding);
}
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
block:
{
if(condition) break block;
// rest of code that won't be executed if condition is true
}
outterLoop: for(int i = 0; i < 10; i++)
{
while(condition)
{
if(someConditon) break outterLoop; // 结束for循环
if(anotherConditon) break; // 结束while循环
// more code
}
// more code
}