如何更新 RecyclerView 项目内的绑定?

Duo*_*uos 3 android android-databinding android-livedata android-viewmodel

想象一个 StackOverflow 问题的列表,这些问题可以单独被赞成和反对。他们每个人都有一个计数,显示其投票数。单击 upvote 将该计数加一,单击 downvote 将该计数减一。这就是我要实现的目标的总体思路。rv_item.xmlRecyclerView 中单个项目的布局(为简洁起见):

data>
    <variable
        name="item"
        type="com.mycodez.Item"/>

    <variable
        name="vm"
        type="com.mycodez.ListViewModel" />
</data>

<Button
    android:id="@+id/decrease_quantity"
    android:onClick="@{() -> vm.decrementItemQuantity(item)}"/>

<TextView
    android:id="@+id/quantity"
    android:text="@{String.valueOf(item.quantity)}"
    tools:text="1" />

<Button
    android:id="@+id/increase_quantity"
    android:onClick="@{() -> vm.incrementItemQuantity(item)}"/>
Run Code Online (Sandbox Code Playgroud)

Where@+id/decrease_quantity类似于downvote,@+id/quantity类似于投票计数,@+id/increase_quantity类似于upvote。适配器:

class ListRvAdapter extends RecyclerView.Adapter<ListRvAdapter.ViewHolder> {
    private List<Item> items;
    private ListViewModel viewModel;
    private LifecycleOwner lifecycleOwner;

    ListRvAdapter(List<Item> items, ListViewModel viewModel, LifecycleOwner lifecycleOwner) {
        this.items = items;
        this.viewModel = viewModel;
        this.lifecycleOwner = lifecycleOwner;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        RvItemBinding binding = RvItemBinding.inflate(layoutInflater, parent, false);
        return new ViewHolder(binding, viewModel, lifecycleOwner);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bind(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final RvItemBinding binding;
        private ListViewModel viewModel;
        private LifecycleOwner lifecycleOwner;

        ViewHolder(RvItemBinding binding, ListViewModel viewModel, LifecycleOwner lifecycleOwner) {
            super(binding.getRoot());
            this.binding = binding;
            this.viewModel = viewModel;
            this.lifecycleOwner = lifecycleOwner;
        }

        public void bind(Item item) {
            binding.setItem(item);
            binding.setVm(viewModel);
            binding.executePendingBindings();
            binding.setLifecycleOwner(lifecycleOwner);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我省略视图模型,ListViewModel因为什么vm.decrementItemQuantity(item)vm.incrementItemQuantity(item)实际上做的是调用模型更新数据库中的数量:

@Query("UPDATE items SET quantity=quantity + 1 WHERE id=:itemId")
Completable incrementQuantity(int itemId);

@Query("UPDATE items SET quantity=quantity - 1 WHERE quantity > 1 AND id=:itemId")
Completable decrementQuantity(int itemId);
Run Code Online (Sandbox Code Playgroud)

因此,虽然模型中的数量正在更新,但我不清楚我应该如何更新该特定 RecyclerView 项目的 UI 以反映该更改。通常,当使用 LiveData 和 DataBinding 时,我会做一些类似的事情itemQuantityLiveData.setValue(item.quantity),这会立即反映在 XML 中。但是在这种情况下,我bind(Item item)在 ViewHolder 中使用了该方法,并且该item对象作为变量而不是表达式传递给 DataBinding 布局。

我该如何解决这个问题?

Duo*_*uos 5

没有必要想太多。调用notifyItemChanged(position)适配器实例是缺失的部分。所以这就变成了如何将 RecyclerView 项的索引传回适配器的问题。

编辑rv_item.xml以获取一个额外的变量,position

<data>
    <variable
        name="item"
        type="com.mycodez.Item"/>

    <!-- add this variable -->
    <variable
        name="position"
        type="int"/>

    <variable
        name="vm"
        type="com.mycodez.ListViewModel" />
</data>
Run Code Online (Sandbox Code Playgroud)

此外,在同一个 XML 文件中,传递positionvmonClick以下触发的方法:

<Button
    android:id="@+id/decrease_quantity"
    android:onClick="@{() -> vm.decrementItemQuantity(item, position)}"/>

<Button
    android:id="@+id/increase_quantity"
    android:onClick="@{() -> vm.incrementItemQuantity(item, position)}"/>
Run Code Online (Sandbox Code Playgroud)

那么里面的bind方法ListRvAdapter ViewHolder就变成了:

public void bind(Item item, int position) { // new argument
    binding.setItem(item);
    binding.setPosition(position); // pass position to the layout
    binding.setVm(viewModel);
    binding.executePendingBindings();
    binding.setLifecycleOwner(lifecycleOwner);
}
Run Code Online (Sandbox Code Playgroud)

在适配器中传递新参数,onBindViewHolder如下所示:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    holder.bind(items.get(position), position);
}
Run Code Online (Sandbox Code Playgroud)

最后,在ListRvAdapterfor分别增加和减少数量上增加两个方法:

public void increaseQuantity(int position) {
    Item item = items.get(position);
    item.quantity += 1;
    notifyItemChanged(position);
}

public void decreaseQuantity(int position) {
    Item item = items.get(position);
    item.quantity -= 1;
    notifyItemChanged(position);
}
Run Code Online (Sandbox Code Playgroud)

有了所有这些,当按钮@+id/increase_quantity被点击时,vm.incrementItemQuantity(item, position)就会被触发。在 MVVM 设置中,ViewModel 将有一个 LiveData 事件对象position

public MutableLiveData<Event<Integer>> itemQuantityIncreased = new MutableLiveData<>(); 
Run Code Online (Sandbox Code Playgroud)

调用vm.incrementItemQuantity(item, position)将设置该对象的值:

itemQuantityIncreased.setValue(new Event<>(position));

最后,View 将在 ViewModel 的itemQuantityIncreasedLiveData 对象上有一个观察者。因为事件会随其发送 RecyclerView 项目的位置,所以视图可以获取该位置并adapter.increaseQuantity(position)在适当的地方调用。adapter.decreaseQuantity(position)工作原理相同。