Diff util 不更新回收器视图中的项目 UI

Azi*_*asi 3 android kotlin

我正在使用带有回收器视图的列表适配器,现在数据类包含一个变量 isChecked,这用于指示用户是否选择了该变量,代码正在更新列表,因为我可以看到日志(我有出于测试目的而放置)返回当前列表始终在用户单击某个项目时更新,但由于某种原因,UI 中的更改(基于 isChecked 变量)仅在滚动回收器视图或单击其他项目时反映。我放置了一个notifyDataSetChanged来查看它是否强制列表更新并查看更新的视图是否正确并且它有效,但这破坏了使用diff util的整个目的。我的包装数据类中有一个嵌套列表,正在 diff util 中进行比较,如下所示

private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<MainDataClass>() {
            override fun areItemsTheSame(
                oldItem: MainDataClass,
                newItem: MainDataClass
            ): Boolean {
                if (oldItem is MainDataClass.Item && newItem is MainDataClass.Item) {
                    return oldItem.data.id == newItem.data.id
                } else if (oldItem is MainDataClass.List && newItem is MainDataClass.List) {
                    return oldItem.list == newItem.list
                } else return false
            }

            override fun areContentsTheSame(
                oldItem: MainDataClass,
                newItem: MainDataClass
            ): Boolean {
                if (oldItem is MainDataClass.Item && newItem is MainDataClass.Item) {
                    return oldItem.data == newItem.data
                } else if (oldItem is MainDataClass.List && newItem is MainDataClass.List) {
                    return oldItem.list == newItem.list
                } else return false
            }
        }
Run Code Online (Sandbox Code Playgroud)

MainDataClass.List 包含上述特定项目的列表。

public class Item{

    private Integer count;
    private Integer id;
    private String icon_img;
    private String name;
    private String cover_img;
    private String group_name;
    private Integer parent_id;
    private Integer status;
    private boolean checked = false;
    private Integer whatToVisible;

    public Item(Integer count, Integer id, String icon_img, String cover_img, String group_name, Integer parent_id) {
        this.count = count;
        this.id = id;
        this.icon_img = icon_img;
        this.cover_img = cover_img;
        this.group_name = group_name;
        this.parent_id = parent_id;
        this.checked = false;
    }

    public Item(Integer id, String icon_img, String group_name, Integer parent_id, boolean checked) {
        this.id = id;
        this.icon_img = icon_img;
        this.name = name;
        this.group_name = group_name;
        this.parent_id = parent_id;
        this.checked = checked;
    }

    public Item(Integer id,Integer parent_id) {
        this.id = id;
        this.parent_id = parent_id;
    }

    public static Item objectExample() {
        return new TrendingGroupsResponse(-2, -2);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Integer getCount() {
        return count;
    }

    public String getIcon_img() {
        return icon_img;
    }

    public void setIcon_img(String icon_img) {
        this.icon_img = icon_img;
    }

    public String getCover_img() {
        return cover_img;
    }

    @Override
    public int hashCode() {
        return this.id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Item)) return false;
        Item that = (Item) o;
        return checked == that.checked && Objects.equals(count, that.count) && Objects.equals(id, that.id) && Objects.equals(icon_img, that.icon_img) && Objects.equals(name, that.name) && Objects.equals(cover_img, that.cover_img) && Objects.equals(group_name, that.group_name) && Objects.equals(parent_id, that.parent_id) && Objects.equals(status, that.status) && Objects.equals(whatToVisible, that.whatToVisible);
    }

    @Override
    public String toString() {
        return "id: " + this.id;
    }

    public void setCover_img(String cover_img) {
        this.cover_img = cover_img;
    }

    public String getGroup_name() {
        if (name != null){
            group_name = name;
        }
        return group_name;
    }

    public void setGroup_name(String group_name) {
        this.group_name = group_name;
    }

    public Integer getParent_id() {
        return parent_id;
    }

    public void setParent_id(Integer parent_id) {
        this.parent_id = parent_id;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getIconImg() {
        return icon_img;
    }

    public void setIconImg(String icon_img) {
        this.cover_img = icon_img;
    }

    public String getCoverImg() {
        return cover_img;
    }

    public void setCoverImg(String cover_img) {
        this.cover_img = cover_img;
    }

    public String getGroupName() {
        if (name != null) {
            group_name = name;
        }
        return group_name;
    }

    public void setGroupName(String group_name) {
        this.group_name = group_name;
    }

    public Integer getWhatToVisible() {
        return whatToVisible;
    }

    public void setWhatToVisible(Integer whatToVisible) {
        this.whatToVisible = whatToVisible;
    }
Run Code Online (Sandbox Code Playgroud)

从主列表中单击主项目时调用的方法

fun addOrRemoveSelectedItemsIfPresent(item: Item) {
        viewModelScope.launch {
            addOrRemoveItemsFromPopularItems(item.id.toString())
            updateAllItems(item)
            var itemList = _selectedItems.value
            if (itemList == null) itemList = ArrayList()
            itemList.forEach { item1: Item ->
                if (item1.id == item.id) {
                    itemList.remove(group)
                    selectedItemCount.set(selectedItemCount.get() - 1)
                    _selectedItems.value = itemList
                    return@launch
                }
            }
            itemList.add(response)
            selectedItemCount.set(selectedItemCount.get() + 1)
            _selectedItems.value = itemList
        }
    }

private fun addOrRemoveItemsFromPopularItems(id: String) {
        val popularItems = _popularItemsLiveData.value?.data
        popularItems?.forEach {
            if (it.id.toString() == id) {
                if (it.isChecked == null || it.isChecked == false) {
                    it.isChecked = true
                } else {
                    it.isChecked = false
                }
            }
        }
        _popularItemsLiveData.postValue(Success(popularItems))
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ini 6

让我们在这里分解一下这个问题:

  1. 数据来自 API。
  2. 数据成为List<Things>(我们称它们为“事物”以保护其身份)。
  3. 您的用户界面显示两个列表,其中一个列表包含您收到的所有物品。我们称这个为allList
  4. 您还希望根据用户从中选择的项目在另一个 recyclerView 中显示此列表的子集allList。我们称之为selectedList. (嘿,我并没有说我擅长命名事物)。

现在的关键是当您点击 中的项目时会发生什么allListselectedList如果该项目不存在,您希望将其添加到列表中;或者如果已选择该项目,则将其从列表中删除。

这就是你需要发生的事情。

我将这样做:

  1. 用户点击一个项目,我们将此事件与该项目或其 ID 一起传递给 VM(视图模型)。(我会传递整个项目,因为你已经拥有它,但它无关紧要)。
  2. VM 现在必须调用用例或类似用例来更新所选项目列表 ( selectedList)。虚拟机并不真正关心该列表中的内容,这不是它的工作。这就是用例/委托。但是让我们暂时忽略抽象的好处,并假设虚拟机有一个可以为您做到这一点的方法:

fun updateSelectedList(selectedItem: Thing): List<Thing> //remember we call 'em "things".

该方法内部发生的情况尚不相关

然后,您通过 liveData 将此列表传递到您的 Fragment/UI,它将submitList(...)。这一切都有效。您还在isChecked某处更新了项目的状态,因此allList也可以更新其“视图状态”以选中或取消选中视图。

想象一个解决方案

在我们讨论潜在问题之前,假设您有一个完美的代码库,其中返回了updateSelectedList您提交到 recyclerView 的列表,并且它工作正常(与您allList现在声称的工作正常一样)。

在这个完美的场景中,DiffUtil 不会发现自己遇到任何麻烦,如果一个项目是新的(或消失),它将能够计算差异并每次在正确的位置呈现适当的视图,就像它经常做的那样。

问题

那么问题可能出在哪里呢?

您声称在调用 DiffUtil 时, 的值isChecked已经更改,因此 diffUtil 不会检测到差异。

这让我相信,也是我写这个冗长答案的原因,您传递给每个适配器的数据是相同的,因此当您修改 to selectedListbe中的项目时isSelected = true/false,您实际上是在修改相同的引用/包含的对象allList

为了更好地说明这一点,我制作了一个 Kotlin Playground,并用一个示例来说明我的意思,可以在 play.kotlinlang.org 中找到它

那么实际怀疑的问题是什么?

我相信您执行步骤的顺序会导致数据发生变异,当 RecyclerView 需要计算更改时,它们已经发生了。

你能做什么?

我看待这样的问题的方式是:

  1. 关于列表及其状态应该有单一的事实来源。
  2. 提供给适配器的所有列表(全部和选定的)应该是不可变的副本。
  3. 当一个事件发生时,将改变任一列表(我假设它们同时发生改变,因为当您选择或取消选择一个项目时,另一个列表也必须反映更改),ViewModel,通过用例/存储库将执行转换并发出两个新的不可变列表,其中值已修改。

换句话说。

您选择(或取消选择)“所有列表”中的一个项目,您的虚拟机评估需要执行的操作,并生成一个新的不可变列表并复制该项目。

您链接的答案建议使用toList()它来创建新列表,但这是浅复制,而不是深复制,因此项目仍然相同,改变的是实际列表。意义:

val item1 = "A"
val item2 = "B"
val list1 = mutableList(item1, item2)
val list2 = list1.toList()
Run Code Online (Sandbox Code Playgroud)

其中 ^^^ list2[0] 和 list2[1] 分别指向与 list1[0] 和 [1] 相同的对象。如果更改 item1,则会在两个列表中更改它。

我怀疑这就是发生在你身上的事情。

再说一次,不要只做一个toList。从最好是不可变的对象构造一个新的不可变列表(因此使用data class并使用 val,而不是 var)。

最好这样做:

val modifiedItem = oldItem.copy(isSelected = true)

我认为所有这些数据转换(修改两个列表和项目)应该在 ViewModel 中几乎同时发生(如果需要,可以委托给用例)。

我希望这能给你一个有用的方向推动。

请记住:关注点分离是你的朋友。在您的完美世界中,适配器的工作是计算列表 A 和列表 B 之间的差异并渲染它们。而已。

在选定的列表中,所发生的只是项目出现并重新出现(基于它们是否被选择)。该列表应该简单地由所选项目组成(不需要是副本,只要您不以任何方式改变它们,只需显示它们)。

all列表中,项目大多保持在相同的位置,但它们的内容发生变化(以指示它们是否被选择)。同样,这是列表接收的只读对象的不可变列表。

想想这个场景:

您的all list包含 2 件物品。

两者均未选择。

您选择项目“A”(第一个)。如果您做对了,您收到的下一个列表是:

  1. 包含 1 项的选定列表。(供选择)
  2. 一个新的“全部列表”,包含 2 项,其中item[0](第一项)是isSelected = true。这将与之前 isSelected 为 false 的列表进行比较。它应该有效。

我怎么知道这有效?

几年前我不得不做一件非常类似的事情;)