一、什么是ViewBinding
ViewBinding是Android Studio 3.6推出的新特性,旨在替代findViewById(内部实现还是使用findViewById)。通过ViewBinding,可以更轻松地编写可与视图交互的代码。在模块中启用ViewBinding之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
二、ViewBinding的优势
与使用findViewById相比,ViewBinding有明显的优势:
1.类型安全:ViewBinding 生成的属性类型和布局中的View类型是一致的,不需要进行类型转换,相对于findViewById有类型安全性。
//findViewById需要类型转换
TextView textView=(TextView) findViewById(R.id.text_view);
//ViewBinding不需要类型转换
binding.textView.setText("Hello");
2.减少空指针异常:ViewBinding可以直接访问绑定类中的视图,因此不存在因 view ID 找不到而引发空指针异常的风险。
3.代码更简洁:使用ViewBinding只需要获取一次实例,就可以实现对所有控件的调用,相对于findViewById不用多次获取实例,代码更简洁。
三、ViewBinding的使用
1.使用前提
1.1添加依赖
在 app目录下的的 build.gradle 文件中,添加如下代码:
android {
...
buildFeatures {
viewBinding true
}
}
如果你的 build.gradle 是 build.gradle.kts 这种文件,则这样添加代码:
android {
...
buildFeatures {
viewBinding = true
}
}
添加后点击Sync Now进行同步工程,完成配置
1.2生成绑定类
完成第一步后点击编译后自动生成绑定类
位置如图所示:
绑定类的命名规则:
将xml文件名转化为驼峰命名法,即去掉下划线并将每个单词首字母大写,例如:
布局文件名:activity_main.xml
生成绑定类名:ActivityMainBinding
默认情况下,AS会对工程中的所有xml文件生成绑定类。如果不想为某个布局文件生成,则可以将 tools:viewBindingIgnore=“true” 属性添加到该布局文件的根视图中,例如:
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
2.使用ViewBinding
ViewBinding可以用在各种需要布局与代码交互的地方,如Activity、Fragment、ViewHolder等
2.1在Activity中使用ViewBinding
在布局文件中,我们设定了两个控件TextView和Button,不需要有任何修改
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="136dp"
android:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="92dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 如果使用findViewById,我们需要多次获取实例:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView=(TextView) findViewById(R.id.text_view);
Button button1=(Button) findViewById(R.id.button1);
}
}
- 如果使用ViewBinding:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding=ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.textView.setText("Hello");
binding.button1.setOnClickListener(v -> {
Toast.makeText(MainActivity.this,"Button1",Toast.LENGTH_SHORT).show();
});
}
}
可以发现只用获取一次实例就可以操作所有控件
1.使用inflate方法:inflate是ViewBinding提供的静态方法,用于将布局文件解析成对应的视图对象。getLayoutInflater用于获取LayoutInflater对象,该对象可以将XML文件转换为视图。
2.创建绑定对象:会创建一个ActivityMainBinding类绑定对象。这个对象包含了对activity_main.xml布局文件中所有视图的引用,可以通过这个对象直接访问和操作视图。
2.2在Adapter中使用ViewBinding
在使用RecyclerView中,我们在自定义适配器中也有许多运用到findViewById的地方,可以用ViewBinding替代。
- 如果使用findViewById
package com.example.viewbinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<String> mFruitList;
public FruitAdapter(List<String> fruits){
mFruitList=fruits;
}
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}
public void onBindViewHolder(ViewHolder holder,int position){
String fruitname=mFruitList.get(position);
holder.textView.setText(fruitname);
}
public int getItemCount(){
return mFruitList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(View view) {
super(view);
textView = (TextView) view.findViewById(R.id.fruit_name);
}
}
}
- 如果使用ViewBinding
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private List<String> mFruitList; private LayoutInflater inflater; public FruitAdapter(Activity activity,List<String> fruits){ mFruitList=fruits; inflater=LayoutInflater.from(activity); } public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ RecyclerViewBinding binding = RecyclerViewBinding.inflate(inflater, parent, false); ViewHolder holder=new ViewHolder(binding); return holder; } public void onBindViewHolder(ViewHolder holder,int position){ String fruitname=mFruitList.get(position); holder.textView.setText(fruitname); } public int getItemCount(){ return mFruitList.size(); } static class ViewHolder extends RecyclerView.ViewHolder { TextView textView; private RecyclerViewBinding binding; public ViewHolder(RecyclerViewBinding binding) { super(binding.getRoot()); textView = binding.fruitName; } } }
2.3在布局中嵌套include标签
如果布局中存在嵌套,比如使用 include 标签引用了另一个布局,这时就没法直接用XXXbinding对象去引用嵌套布局里的id了。
解决方法:
- 为include标签添加id;
- 使用 binding 访问到 include 节点,再访问到 include节点内部的其他控件。
再举例说明一下,在activity_main.xml中我们用include标签引用了一个布局 title_bar.xml,同时为它添加了id
<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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<include
android:id="@+id/include_title_bar"
layout="@layout/title_bar" />
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="136dp"
android:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="92dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
title_bar.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello"/>
</LinearLayout>
在Activity中访问:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding=ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.textView.setText("Hello");
binding.includeTitleBar.textHello.setText("Hello");
binding.button1.setOnClickListener(v -> {
Toast.makeText(MainActivity.this,"Button1",Toast.LENGTH_SHORT).show();
});
}
}
这样我们就实现了使用ViewBinding完成布局嵌套。