将DataBinding整合到ViewHolder的一种极简方式

941 阅读2分钟

自从DataBinding和ViewBinding出现后,Android开发中获取界面元素就变得非常方便。即使是RecyclerViewViewHolder也可以使用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后,进行以下步骤:

  1. ImageViewHolder继承ViewBindingHolder
  2. HolderImage作为ViewBindingHolder的泛型参数;
  3. 编写/自动生成一个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);
    }
}