自从DataBinding和ViewBinding出现后,Android开发中获取界面元素就变得非常方便。即使是RecyclerView
的ViewHolder
也可以使用ViewBinding,例如下面这样
public class ItemTestHolder extends RecyclerView.ViewHolder {
private final HolderItemTestBinding binding;
public static ItemTestHolder newInstance(ViewGroup viewGroup) {
HolderItemTestBinding binding = HolderItemTestBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false);
return new ItemTestHolder(binding);
}
public ItemTestHolder(@NonNull HolderItemTestBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void setData(String text){
binding.tvName.setText(text);
}
}
或者
public class ItemTestHolder extends RecyclerView.ViewHolder {
private final HolderItemTestBinding binding;
public ItemTestHolder(@NonNull ViewGroup viewGroup) {
super(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.holder_item_test, viewGroup, false));
this.binding = HolderItemTestBinding.bind(itemView);
}
public void setData(String text) {
binding.tvName.setText(text);
}
}
但是,像这样每次写一遍....inflate(...)
的代码也是一件挺麻烦的事情,因此本文就来介绍一种极简的方式来整合ViewBinding和ViewHolder,最终实现像下面这样的代码:
public class ItemTestHolder extends ViewBindingHolder<HolderItemTestBinding> {
public ItemTestHolder(@NonNull ViewGroup viewGroup) {
super(viewGroup);
}
public void setData(String text) {
getBinding().tvName.setText(text);
}
}
ViewBindingHolder基类的实现
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.yr.corelib.util.ReflectUtils;
import java.lang.reflect.*;
@Keep
public abstract class ViewBindingHolder<B extends ViewBinding> extends RecyclerView.ViewHolder {
private final B binding;
public ViewBindingHolder(@NonNull ViewGroup viewGroup) {
super(viewGroup);
try {
Class<? extends ViewBinding> aClass = ReflectUtils.findParameterizedClass(getClass().getGenericSuperclass(), ViewBinding.class);
Method inflateMethod = aClass.getMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
try {
binding = (B) inflateMethod.invoke(null, LayoutInflater.from(viewGroup.getContext()), viewGroup, false);
modifyItemView(binding.getRoot());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
throw new RuntimeException("Something wrong when create Binding:", e);
}
}
public B getBinding() {
return binding;
}
private void modifyItemView(Object newFieldValue) throws Exception {
Field field = RecyclerView.ViewHolder.class.getDeclaredField("itemView");
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(this, newFieldValue);
}
}
2. ReflectUtils反射工具
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class ReflectUtils {
public static <T> Class<? extends T> findParameterizedClass(Class<?> aClass, Class<T> targetClass) {
return findParameterizedClass(aClass.getGenericSuperclass(), targetClass);
}
public static <T> Class<? extends T> findParameterizedClass(Type type, Class<T> targetClass) {
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
for (Type typeArgument : typeArguments) {
if (typeArgument instanceof Class) {
if (targetClass.isAssignableFrom((Class<?>) typeArgument)) {
return (Class<? extends T>) typeArgument;
}
} else {
throw new IllegalStateException("typeArgument is not a Class");
}
}
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType.equals(Object.class)) {
return null;
} else {
return findParameterizedClass(rawType, targetClass);
}
} else if (type instanceof Class) {
if (type.equals(Object.class)) {
return null;
} else {
Type superclass = ((Class<?>) type).getGenericSuperclass();
return findParameterizedClass(superclass, targetClass);
}
} else {
throw new IllegalStateException("type is not ParameterizedType or Class!");
}
}
}
3. 用法说明
ViewBindingHolder
的使用非常简单,假设你要新建一个名为ImageViewHolder
,而布局文件名为holder_image.xml
(对应ViewBinding类为HolderImage
),那么在配置好ViewBinding后,进行以下步骤:
- 让
ImageViewHolder
继承ViewBindingHolder
; - 将
HolderImage
作为ViewBindingHolder
的泛型参数; - 编写/自动生成一个
ViewHolder
的默认构造方法;
然后就可以通过getBinding
来获取对应的ViewBinding对象了。
当然,也可以不用显式继承,而是匿名内部类的方式来使用ViewBindingHolder
,例如:
RecyclerView.Adapter<ViewBindingHolder<HolderItemTestBinding>> adapter = new RecyclerView.Adapter<ViewBindingHolder<HolderItemTestBinding>>() {
@NonNull
@Override
public ViewBindingHolder<HolderItemTestBinding> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewBindingHolder<HolderItemTestBinding>(parent) {
};
}
@Override
public void onBindViewHolder(@NonNull ViewBindingHolder<HolderItemTestBinding> holder, int position) {
holder.getBinding().tvName.setText("");
}
@Override
public int getItemCount() {
return 1;
}
};
4. DataBindingHolder的实现
同理,DataBinding也可以采用这种方式封装成一个DataBindingHolder
:
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.RecyclerView;
import com.yr.corelib.util.ReflectUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Keep
public abstract class DataBindingHolder<B extends ViewDataBinding> extends RecyclerView.ViewHolder {
private final B binding;
public DataBindingHolder(@NonNull ViewGroup viewGroup) {
super(viewGroup);
try {
Class<? extends ViewDataBinding> aClass = ReflectUtils.findParameterizedClass(getClass().getGenericSuperclass(), ViewDataBinding.class);
Method inflateMethod = aClass.getMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
try {
binding = (B) inflateMethod.invoke(null, LayoutInflater.from(viewGroup.getContext()), viewGroup, false);
modifyItemView(binding.getRoot());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
throw new RuntimeException("Something wrong when create Binding:", e);
}
}
public B getBinding() {
return binding;
}
public void bind(int position) {
}
private void modifyItemView(Object newFieldValue) throws Exception {
Field field = RecyclerView.ViewHolder.class.getDeclaredField("itemView");
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(this, newFieldValue);
}
}