一、介绍ViewBinding
介绍
ViewBinding 是 Android 开发中一种用于绑定视图的方法,它允许开发者更安全、更简洁地访问布局中的视图。在传统的开发模式中,我们通常使用 findViewById
来获取布局中的视图,但这种方式容易出错,且代码冗长。ViewBinding 通过生成绑定类,提供了一种更优雅的方式来访问视图。
与DataBinding对比
DataBinding:
- DataBinding 是一个 Android 库,它允许开发者将布局中的 UI 组件绑定到应用的数据源。
- 它通过 XML 布局文件中的绑定表达式来实现数据和视图之间的自动同步。
- DataBinding 可以减少在 Activity 或 Fragment 中设置视图和更新 UI 的样板代码。
- 它支持双向数据绑定,即 UI 的变化可以自动更新到数据源,反之亦然。
- 使用 DataBinding 需要在
build.gradle
文件中添加相应的依赖,并在布局文件中使用特定的语法。
ViewBinding:
- ViewBinding 是 Android Jetpack 的一部分,用于在编译时生成绑定类,从而以编程方式安全地访问视图。
- 它提供了一种更简单和更安全的方式来访问布局中的视图,而不需要使用
findViewById
。 - ViewBinding 不涉及数据绑定,它只关注视图的访问。
- 使用 ViewBinding 同样需要在
build.gradle
文件中添加依赖,并且在编译时自动生成绑定类。 - ViewBinding 通常与 LiveData 和 ViewModel 结合使用,以实现响应式编程。
如何启用
在模块的 build.gradle
文件中添加以下代码,同步后即可启用。
android {
// ...
viewBinding {
enabled = true
}
}
二、Binding类分析
布局文件展示
<?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="wrap_content"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/textView9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="TextView"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="TextView"
android:textColor="@color/text_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ItemProductdetailsInfoBinding 分析
ItemProductdetailsInfoBinding
是由 View Binding 编译器自动生成的绑定类,它对应于 item_productdetails_info.xml
布局文件。以下是该类中的关键方法及其作用:
getRoot()
: 返回布局的根视图,通常是一个ConstraintLayout
或其他布局容器。inflate()
: 静态方法,用于将布局文件实例化为视图。它接受LayoutInflater
作为参数,并且可以指定父视图和是否附加到父视图。bind()
: 静态方法,用于绑定视图到ItemProductdetailsInfoBinding
类的实例。这个方法内部通过ViewBindings.findChildViewById
来查找布局中的子视图,并将其赋值给绑定类的相应字段。
二、封装思路
封装 RecyclerView 的 Adapter 时,我们希望简化创建流程,减少冗余代码。使用 ViewBinding 可以让我们更方便地访问布局中的视图,而封装 Adapter 则可以让我们将数据和视图的绑定逻辑集中管理。
封装步骤
- 定义泛型: 定义两个泛型参数,
T
表示数据类型,VB
表示对应的 ViewBinding 类型。public abstract class BindingAdapter<T, VB extends ViewBinding> extends RecyclerView.Adapter<BindingAdapter.BindingViewHolder<VB>>
- 存储数据: 在 Adapter 中维护一个数据列表
mData
。private List<T> mData; public BindingAdapter(List<T> mData) { this.mData = mData; } @Override public int getItemCount() { return mData.size(); }
- 创建 ViewHolder: 重写
onCreateViewHolder
方法,使用 ViewBinding 创建 ViewHolder。通过抽象方法onCreateViewBinding
实现与具体的布局文件绑定。@NonNull @Override public BindingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new BindingViewHolder(onCreateViewBinding(LayoutInflater.from(parent.getContext()), parent)); }
- 绑定数据: 重写
onBindViewHolder
方法,将数据绑定到 ViewHolder 的视图上。由于必须使用ViewBinding具体类的inflate
方法,所以设置一个抽象方法onConvert
,具体在使用时实现。@Override public void onBindViewHolder(@NonNull BindingAdapter.BindingViewHolder<VB> holder, int position) { onConvert(holder.getBinding(), mData.get(position)); } protected abstract void onConvert(VB binding, T itemData);
三、具体应用实例
假设我们有一个商品详情列表,每个列表项需要显示商品名称和描述。我们可以按照以下步骤实现:
1. 定义数据模型
import android.annotation.SuppressLint;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class GoodItem {
@SerializedName("home_good_item")
private List<HomeGoodItemDTO> homeGoodItem;
public List<HomeGoodItemDTO> getHomeGoodItem() {
return homeGoodItem;
}
public void setHomeGoodItem(List<HomeGoodItemDTO> homeGoodItem) {
this.homeGoodItem = homeGoodItem;
}
public static class HomeGoodItemDTO {
@SerializedName("game_icon")
private String gameIcon;
@SerializedName("game_name")
private String gameName;
@SerializedName("good_title")
private String goodTitle;
@SerializedName("good_price")
private double goodPrice;
@SerializedName("purchased_number")
private String purchasedNumber;
public String getGameIcon() {
return gameIcon;
}
public void setGameIcon(String gameIcon) {
this.gameIcon = gameIcon;
}
public String getGameName() {
return gameName;
}
public void setGameName(String gameName) {
this.gameName = gameName;
}
public String getGoodTitle() {
return goodTitle;
}
public void setGoodTitle(String goodTitle) {
this.goodTitle = goodTitle;
}
@SuppressLint("DefaultLocale")
public String getGoodPrice() {
return String.format("¥%.2f",goodPrice);
}
public void setGoodPrice(double goodPrice) {
this.goodPrice = goodPrice;
}
public String getPurchasedNumber() {
return purchasedNumber+"人已购买";
}
public void setPurchasedNumber(String purchasedNumber) {
this.purchasedNumber = purchasedNumber;
}
}
}
2. 创建 Adapter
继承 BindingAdapter
并实现抽象方法:
private class GoodItemAdapter extends BindingAdapter<GoodItem.HomeGoodItemDTO, ItemLayoutHomeGoodsBinding> {
public GoodItemAdapter(List<GoodItem.HomeGoodItemDTO> mData) {
super(mData);
}
@Override
protected ItemLayoutHomeGoodsBinding onCreateViewBinding(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return ItemLayoutHomeGoodsBinding.inflate(inflater, parent, false);
}
@Override
protected void onConvert(ItemLayoutHomeGoodsBinding binding, GoodItem.HomeGoodItemDTO itemData) {
Glide.with(getContext())
.load(itemData.getGameIcon())
.into(binding.gameImage);
binding.itemTitle.setText(itemData.getGoodTitle());
binding.price.setText(itemData.getGoodPrice());
binding.purchasedNumber.setText(itemData.getPurchasedNumber());
binding.gameName.setText(itemData.getGameName());
}
}
3. 使用 Adapter
在 Activity 或 Fragment 中设置 RecyclerView 的 Adapter:
这里的数据通过网络请求获取,通过方法response.body().getHomeGoodItem()
返回数据模型类的List。
homeGoodItemEnqueue.enqueue(new Callback<>() {
@SuppressLint("NotifyDataSetChanged")
@Override
public void onResponse(Call<GoodItem> call, Response<GoodItem> response) {
List<GoodItem.HomeGoodItemDTO> homeGoodItem = response.body().getHomeGoodItem();
RecyclerView goodsRecyclerView = mBinding.goodsRecyclerView;
GoodItemAdapter goodItemAdapter = new GoodItemAdapter(homeGoodItem);
goodsRecyclerView.setAdapter(goodItemAdapter);
goodsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
goodItemAdapter.notifyDataSetChanged();
}
@Override
public void onFailure(Call<GoodItem> call, Throwable t) {
NetworkUtils.netErr(view);
}
});
通过这种方式,我们不仅简化了 Adapter 的创建过程,还利用 ViewBinding 减少了对视图的直接操作,提高了代码的可读性和可维护性。
四、封装类整体代码
package com.hnucm.c202201020245.Utils;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import java.util.List;
/**
* 使用viewBinding封装好的RecyclerView.Adaptor,简化了创建流程
*
* @author lingdianshiren
* @param <T> 数据类型
* @param <VB> 布局类型
*/
public abstract class BindingAdapter<T, VB extends ViewBinding> extends RecyclerView.Adapter<BindingAdapter.BindingViewHolder<VB>> {
private List<T> mData;
public BindingAdapter(List<T> mData) {
this.mData = mData;
}
@NonNull
@Override
public BindingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new BindingViewHolder(onCreateViewBinding(LayoutInflater.from(parent.getContext()), parent));
}
@Override
public void onBindViewHolder(@NonNull BindingAdapter.BindingViewHolder<VB> holder, int position) {
onConvert(holder.getBinding(), mData.get(position));
}
@Override
public int getItemCount() {
return mData.size();
}
/**
* 创建itemView方法
* @param inflater
* @param parent
* @return VB.infalte(inflater,parent,false)
*/
protected abstract VB onCreateViewBinding(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
/**
* 布局与数据具体绑定关系以及监听器等
* @param binding
* @param itemData
*/
protected abstract void onConvert(VB binding, T itemData);
public static class BindingViewHolder<T extends ViewBinding> extends RecyclerView.ViewHolder {
private final T mBinding;
public BindingViewHolder(@NonNull T binding) {
super(binding.getRoot());
mBinding = binding;
}
public T getBinding() {
return mBinding;
}
}
}
评论区