如何在RecyclerView项中更新绑定?

4

想象一下一个可以逐个进行投票的StackOverflow问题列表。每个问题都有一个计数器,显示其得票数。点击“赞成”按钮会使得该计数器加1,点击“反对”按钮会使得该计数器减1。这就是我尝试实现的基本思路。单个RecyclerView中每个项目的布局rv_item.xml(为了简洁而压缩):

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)}"/>

@+id/decrease_quantity类似于踩,@+id/quantity类似于投票数,@+id/increase_quantity类似于赞。 适配器:

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);
        }
    }
}

我省略了ViewModel,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);

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

在您的模型上调用 notifyPropertyChanged() 方法。不要忘记从 BaseObservable 类继承。 - Abbas
请进一步阐述。我已经在使用LiveData了。您是建议我改用BaseObservable吗? - Zero
否则,我已经在我的问题底部添加了更多信息。请检查。 - Zero
你能展示一下你更新数量字段的代码吗?是在ViewModel还是Model中? - Abbas
啊,没关系。我已经自己解决了这个问题,并且已经将我的解决方案发布为答案。感谢你的光临。 - Zero
1个回答

7

无需过度思考。调用适配器实例上的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>

同样,在XML文件中,通过onClick触发的vm方法中传递position
<Button
    android:id="@+id/decrease_quantity"
    android:onClick="@{() -> vm.decrementItemQuantity(item, position)}"/>

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

那么在 ListRvAdapterViewHolder 中,bind 方法变为:

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);
}

在适配器的 onBindViewHolder 中这样传递新参数:

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

最后,为ListRvAdapter添加两种方法,分别用于增加和减少数量:
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);
}

在所有这些都准备好之后,当单击按钮@+id/increase_quantity时,将触发vm.incrementItemQuantity(item, position)。 在MVVM设置中,ViewModel将具有用于position的LiveData事件对象:

public MutableLiveData<Event<Integer>> itemQuantityIncreased = new MutableLiveData<>(); 

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

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

最后,视图将在ViewModel的 itemQuantityIncreased LiveData对象上有一个观察者。因为Event与RecyclerView项的位置一起发送,因此视图可以获取该位置并在适当的位置调用 adapter.increaseQuantity(position)adapter.decreaseQuantity(position)同样有效。


感谢您详尽的评论。是否真的没有更简单的方法可以直接在viewholder中完成,而不涉及listadapter呢? - obey
上面的解决方案很合适,但我有不同的情况,例如,我从API获取的是查看次数而不是赞和踩,而且我不想通知该位置的整个项目,我只想更新查看次数文本,如何实现? - geek919

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接