[译]Android 数据绑定之: RecyclerView

阅读 1504
收藏 34
2017-01-17
原文链接:github.com

简化, 复用, 重新绑定

有时我会想,“数据绑定”这个名词并不一定特指 Android 中的数据绑定。RecyclerView 就有它独特的方法将其数据绑定到 UI 控件上。它有一个 Adapter,其中需要我们实现两个非常重要的方法来进行数据绑定:

RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType);

void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

RecyclerView 对外暴露出了常见的 ViewHolder 模式 作为其 API 中的第一类公民。在 onCreateViewHolder() 方法中,View 被创建之后,其引用就被包含在 ViewHolder 中以便能被快速配置数据。然后在 onBindView() 方法中,特定的数据即和 View 关联起来。

RecyclerView 中的 Android 数据绑定

前篇文章中指出 , Android 数据绑定可以被看作 ViewHolder 模式。理论上,我们只需在 onCreateViewHolder() 方法中返回生成的绑定类,但是这个类并没有继承 RecyclerView.ViewHolder 类。因此,这个绑定类必须被 ViewHolder ”包含“进去。

public class MyViewHolder extends RecyclerView.ViewHolder {
    private final ItemBinding binding;

    public MyViewHolder(ItemBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }

    public void bind(Item item) {
        binding.setItem(item);
        binding.executePendingBindings();
    }
}

现在,我们的适配器可以使用 Android 数据绑定的方法来创建并绑定数据:

public MyViewHolder onCreateViewHolder(ViewGroup parent,
                                       int viewType) {
    LayoutInflater layoutInflater =
        LayoutInflater.from(parent.getContext());
    ItemBinding itemBinding = 
        ItemBinding.inflate(layoutInflater, parent, false);
    return new MyViewHolder(itemBinding);
}

public void onBindViewHolder(MyViewHolder holder, int position) {
    Item item = getItemForPosition(position);
    holder.bind(item);
}

如果你看得够仔细的话,你可能会看到 MyViewHolder.bind() 方法最后有一个 executePendingBindings() 方法。这会强制绑定操作马上执行,而不是推迟到下一帧刷新时。RecyclerView 会在 onBindViewHolder 之后立即测量 View。如果因为绑定推迟到下一帧绘制时导致错误的数据被绑定到 View 中, View 会被不正确地测量,因此这个 executePendingBindings() 方法非常重要!

复用 ViewHolder

如果你之前曾经使用过 RecyclerView 的 ViewHolder,你会知道我们已经减少了一大堆关于将数据设置到 View 中的代码。但不幸的是,我们仍不得不为不同的 RecyclerView 写一大堆 ViewHolder。另外,如果你有多种 View 类型,你也不清楚如何拓展它。我们可以修复此问题。

通常来说,只有一个数据被传入到绑定类中,例如上文中的 "Item"。当你使用这种模式时,你可以使用类型转换来为各种 RecyclerView 以及各种 View 类型都使用唯一的 ViewHolder。按照惯例我们将单一视图模型对象命名成 "obj"。你也许会命名为 "item" 或者 "data",但如果我使用 "obj",将很容易在例子中辨别。

public class MyViewHolder extends RecyclerView.ViewHolder {
    private final ViewDataBinding binding;

    public MyViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }

    public void bind(Object obj) {
        binding.setVariable(BR.obj, obj);
        binding.executePendingBindings();
    }
}

在 MyViewHolder 中,我使用了 ViewDataBinding,它是所有生成的绑定类的基类,代替了特定的 ItemBinding 类。这样之后,我就能在 ViewHolder 中支持各种各样的布局。我还使用了 setVariable() 方法来取代之前的类型安全,但需要指定特定类型的 setObj() 方式,这样我就能随意指定任何我需要的数据类型了。关键的一点是变量必须命名成 "obj" 因为我使用 BR.obj 作为 setVariable() 的键值。这意味着你必须在你的布局文件中有一个像这样的变量标签:

<variable name="obj" type="Item"/>

当然,你的变量相比于 "Item" ,能使用任何在布局中想要绑定的类型

之后我就能创建一个通用的 RecyclerView 适配器了。

public abstract class MyBaseAdapter
                extends RecyclerView.Adapter<MyViewHolder> {
    public MyViewHolder onCreateViewHolder(ViewGroup parent,
                                           int viewType) {
        LayoutInflater layoutInflater =
                LayoutInflater.from(parent.getContext());
        ViewDataBinding binding = DataBindingUtil.inflate(
                layoutInflater, viewType, parent, false);
        return new MyViewHolder(binding);
    }

    public void onBindViewHolder(MyViewHolder holder,
                                 int position) {
        Object obj = getObjForPosition(position);
        holder.bind(obj);
    }

    @Override
    public int getItemViewType(int position) {
        return getLayoutIdForPosition(position);
    }

    protected abstract Object getObjForPosition(int position);

    protected abstract int getLayoutIdForPosition(int position);
}

在这个适配器中,布局的 ID 被用作 view 类型,这样能更方便得来获取正确的绑定类,同时也能让适配器处理任意数量的布局。但最通用的做法是 RecyclerView 只有一个布局,因此我们可以写这样一个基类:

public abstract class SingleLayoutAdapter extends MyBaseAdapter {
    private final int layoutId;

    public SingleLayoutAdapter(int layoutId) {
        this.layoutId = layoutId;
    }

    @Override
    protected int getLayoutIdForPosition(int position) {
        return layoutId;
    }
}

还剩下什么?

所有 RecyclerView 中的模板现在都被处理完了,留给你做的是最困难的部分:在非 UI 线程加载数据,当数据更新时通知适配器等等。Android 数据绑定仅简化了无聊的部分。

你也可以扩展这个技术来支持多个变量。通常来说需要支持一个事件处理对象来处理例如点击事件等,你也许会想将其传入到视图模型类中。如果你经常在 Activity 或 Fragment 中传值,你可以添加这些变量。只要你使用连贯的命名,你就可以在所有 RecyclerView 中使用这项技术。

使用 Android 数据绑定结合 RecyclerView 是简便的,能显著减少冗长的代码。也许你的应用只需要一个 ViewHolder 并且你再也不需要重写 onCreateViewHolder() 方法和 onBindViewHolder() 了!

评论