ViewBinding(视图绑定)
ViewBinding是android jetpack的一个特性,他的目的只有一个,就是避免编写findViewById,因为它会为每个XML布局文件生成一个绑定类。绑定类的实例包含在相应布局xml文件中具有ID的所有视图的直接引用。
ViewBinding的优点
- Null安全: 由于视图绑定会创建对视图的直接引用,因此不存在因视图ID无效而引发Null指针异常的风险。
- 类型安全: 减少控件类型转换带来的错误(例如text的id给Button对象带来的运行时错误)
与FindViewById的区别
findViewById编写过于繁琐,还需要进行类型转换,造成类型不安全。
与数据绑定的比较
准备工作
在Module级别的build.gradle文件中:
android {
buildFeatures {
viewBinding = true
}
}
如果要忽略某个布局文件,需要添加 tools:viewBindingIgnore="true" 属性到布局中
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
如何使用:
当开启ViewBinding后,系统会为该模块中每个XML布局文件生成一个绑定类(转换为驼峰命名并在末尾添加Binding),每个绑定类均包含根视图已经由id的所有视图的引用
例如:布局文件名为 activity_main.xml ,生成绑定类为 ActivityMainBinding
- 在Activity中使用
使用流程:
- 开启视图绑定功能后,系统会为该模块中的XML布局生成一个绑定类,每个绑定类都包含根布局和具有ID布局的引用。
- 调用绑定类的inflate() 方法获取绑定类对象。
- 调用绑定类对象的getRoot() 方法获取根布局传递到 setContentView()。
XML布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定" />
</LinearLayout>
Activity类:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.tvName.setText("hello world");
binding.tvAge.setText(String.valueOf(18));
binding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round);
binding.btn.setOnClickListener(view -> {
Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
});
}
}
- 在Adapter中使用
RecyclerView的Item布局(main_item.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<ImageView
android:id="@+id/mainImage"
..... />
<TextView
..... />
</LinearLayout>
Adapter如何使用:
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
private List<Fruit> fruitList;
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView fruitImage;
public TextView fruitName;
public ViewHolder(MainItemBinding binding) {
super(binding.getRoot());
fruitImage = binding.img;
fruitName = binding.textView;
}
}
@NonNull
@Override
public MainAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
MainItemBinding binding = MainItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull MainAdapter.ViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.fruitImage.setImageResource(fruit.getFruitImage());
holder.fruitName.setText(fruit.getFruitName());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
- 在Fragment中使用
- 调用绑定类的 inflate() 方法,获取绑定类对象。
- 在调用 getRoot() 方法获取根布局。
- 在 onCreateView() 方法返回根布局,使其成为屏幕上的活动视图。
- 由于 Fragment 的存在时间比视图长。因此需要在 Fragment 的 onDestroyView() 方法中清除对绑定类对象的所有引用。
public class BlankFragment extends Fragment {
private FragmentBlankBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentBlankBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
}
}
- 在引入布局(include)中使用
被引入布局title_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<Button
android:text="include"
android:id="@+id/test_button"
....../>
</LinearLayout>
主布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<!--在include标签中添加id属性-->
<include
android:id="@+id/title_bar"
//注意要写id不然ViewBinding是关联不到title_bar.xml中的控件的。
layout="@layout/title_bar"/>
...
</LinearLayout>
在Activity中去引用:
public class MainActivity extends Activity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//直接使用include标签的id,然后再根据include的id引用include布局里面的id
binding.titleBar.testButton.setText("hello");
}
}
5. 在include的merge标签中使用
- merge:使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味更高的效率。
- merge和普通引入的区别: 首先我们在include的使用不可以再给他id了,因为merge标签并不是一个布局,所以我们无法像刚才那样再include的时候给他指定一个id。
merge标签布局title_bar.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:text="include"
android:id="@+id/test_include"
....../>
</merge>
引入布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<!--在include标签中添加id属性-->
<include
//android:id="@+id/title_bar" //因为merge不能用id
layout="@layout/title_bar"/>
...
</LinearLayout>
Activity利用ViewBinding绑定:
public class MainActivity extends Activity {
private ActivityMainBinding binding;
private TitleBarBinding titleBarBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
//调用TitleBarBinding.bind()让title_bar.xml和我们的activity_main.xml关联起来
titleBarBinding = TitleBarBinding.bind(binding.getRoot());
setContentView(binding.getRoot());
titleBarBinding.testInclude.setText("button");
}
}