在不影响当前可见视图的情况下从 RecyclerView 适配器中删除数据 children

Remove data children from RecyclerView adapter without affecting currently visible view

我正在使用 Recycler 视图构建无限水平滚动列表。 1-2 children 在任何时候都可以在屏幕上看到(因为它们的宽度大于屏幕视口,所以我们永远不会有超过 2 children 可见)。

初始状态:


当用户滚动时(他们可以向前和向后滚动 - 但为了这个例子的动摇,我们假设他们正在滚动 BACKWARDS),我正在获取更多数据,我将它们添加到我的适配器数据中。

然后我会打电话给 notifyItemRangeInserted - 到目前为止一切顺利。

前置后:


剪辑时间:

同时我添加所有这些 children 我检查我们现在是否在数据源中得到 多于 MAX_DATASOURCE_CNT 项。

让我们假设 MAX_DATASOURCE_CNT 在这个例子中是 6

如果我们这样做,我会从数据源的末尾删除不必要的额外 children,然后调用 notifyItemRangeRemoved

我这样做是因为我不想用未使用的数据淹没设备。我现在过度简化了示例,但实际上我的视图可以在用户滚动时累积数千个未使用的数据项,我想 trim 动态地 trim 我的数据源的另一端。

问题:

前置过程发生后,如果用户滚动速度太快,有时 onBindViewHolder(RecyclerView.ViewHolder holder, int position) 会被调用 position == 0(也 holder.getAdapterPosition() == 0

这完全打乱了我的观点,就好像我的观点跳到了一个完全不相关的 child 并显示了那个。

当前解决方法:

我注意到,如果我删除基本上是这些代码行的整个剪辑过程:

if (itemCountAfterPrepend > MAX_DATASOURCE_CNT) {

    int extraItemCnt = itemCountAfterPrepend - MAX_DATASOURCE_CNT;

    // remove every extra item from the end of the array
    removeRange(MAX_DATASOURCE_CNT, itemCountAfterPrepend);

    // and notify our adapter about the removal
    notifyItemRangeRemoved(MAX_DATASOURCE_CNT, extraItemCnt);
}

一切正常。但是我真的很想 trim 我的数据。 我能做什么?

谢谢

好的,我没有找到具体的答案,所以这就是我所做的。

由于问题是 notifyItemRangeRemoved 运行 在绑定视图时,并且由于用户仍在滚动而绑定了视图,因此我从 prepend/append 函数。

相反,我将剪辑过程作为一个独立的函数 (clipExtraDataIfNecessary)。

这只会在 onScrollStateChanged(RecyclerView recyclerView, int newState) 中调用,并且只会在 newState == SCROLL_STATE_IDLE 时调用,即用户停止 滚动:

数据裁剪方法现在如下所示:

/**
 * make sure we never surpass the MAX_DATASOURCE_CNT limit
 * Clips the data ArrayList from the ending (right) side
 */
public void clipExtraEndData() {
    int itemCountAfterPrepend = this.data.size();
    // if we went above our max data source count limit
    if (itemCountAfterPrepend > MAX_DATASOURCE_CNT) {
        int extraItemCnt = itemCountAfterPrepend - MAX_DATASOURCE_CNT;

        synchronized (dataMutationLock) {
            // remove every extra item from the end of the array
            removeRange(MAX_DATASOURCE_CNT, itemCountAfterPrepend);

            // and notify our adapter about the removal
            notifyItemRangeRemoved(MAX_DATASOURCE_CNT, extraItemCnt);
        }
    }
}


/**
 * make sure we never surpass the MAX_DATASOURCE_CNT limit
 * Clips the data ArrayList from the beginning (left) side
 */
public void clipExtraStartData() {
    int itemCountAfterAppend = this.data.size();
    // if we went above our max data-source count limit
    if (itemCountAfterAppend > MAX_DATASOURCE_CNT) {
        int extraItemCnt = itemCountAfterAppend - MAX_DATASOURCE_CNT;

        synchronized (dataMutationLock) {
            // remove every extra item from the beginning of the array
            removeRange(0, extraItemCnt);

            // and notify our adapter about the removal
            notifyItemRangeRemoved(0, extraItemCnt);
        }
    }
}

/**
 * Removes all the extra data that we have accumulated by prepending/appending
 * as the user was scrolling
 *
 * CAUTION: MAKE SURE YOU RUN THIS at a time where the user is NOT scrolling,
 * otherwise we run the risk of bindViewRow being called with preLayout position
 * values (a.k.a 0)
 * @param curVisiblePosition
 */
public void clipExtraDataIfNecessary(int curVisiblePosition) {
    int initialItemCnt = size();
    if (initialItemCnt > MAX_DATASOURCE_CNT) {
        if (curVisiblePosition < initialItemCnt - curVisiblePosition) {
            // we have extra children on the end of our data
            clipExtraEndData();
        } else {
            // we have extra children on the beginning of our data
            clipExtraStartData();
        }
    }
}