5

摘要:尝试通过自定义适配器包装器将标题行动态添加到 ListView。ListView 无法保持滚动位置同步。提供可运行的演示项目。

我想根据 CursorAdapter 中的值将项目动态添加到列表中,在用户当前查看的内容之前几个位置。为此,我有一个适配器,它包装 CursorAdapter 并将新内容索引在 SparseArray 中。将项目添加到自定义适配器时需要更新 ListView,但我遇到了很多试图让它工作的陷阱,并且希望得到一些建议。

演示项目可以在这里下载:DynamicSectionedList.zip

在演示中,通过向前看 10 个位置来动态添加标题,以找到列表项切换到下一个字母的正确位置。notifyDataSetChanged 的​​每个实现都存在如下问题:

Demo 1 这个Demo展示了notifyDataSetChanged()的重要性。单击任何内容时,应用程序都会崩溃。这是由于 ListView 中的一些健全性检查mItemCount != adapter.getItemCount()...。道德是,我们需要通知列表数据已更改。

Demo 2 下一步自然是在发生更改时通知 ListView 更改。不幸的是,在 ListView 滚动时这样做会破坏所有触摸交互,直到应用程序退出触摸模式。您需要“滚动”足够远以生成新标题才能注意到这一点。点击屏幕不会导致滚动停止,一旦停止,列表项将不可点击。这是由于if (!mDataChanged) { /* do very important stuff */ }AbsListView.onTouchEvent() 中的一些代码造成的。

Demo 3 为了解决这个问题,Demo 3 引入了一个 pendingChanges 标志,并且自定义 Adapter 获得了一个 notifyDataSetChangedIfNeeded(),一旦 ListView 进入“安全”状态以进行更改,就可以调用它。必须通知更改的第一点是在 ListView.layoutChildren() 中,因此我重写了该方法,以便在需要时首先通知更改,然后调用。跳过至少一个标题,然后单击一个列表项。

尽管我不完全确定为什么,但这并不完全正确。使用键盘/轨迹球点击或选择一个项目会导致列表刷新,而不会正确同步旧位置。它滚动到列表的顶部,这是不可接受的。

Demo 4 Demo 3 中的滚动问题是可以解决的,至少在触摸模式下是可以的。通过在触摸时添加对 notifyDataSetChangedIfNeeded() 的调用,数据更改恰好发生在所有触摸交互按预期工作并且列表位置正确同步的时间。

但是,当设备不处于触摸模式时,我找不到类似的东西,更不用说它绝对看起来像是一个黑客。该列表几乎总是滚动回顶部,我无法找出导致它偶尔保持正确位置的原因。

由于 Android 在每一步都在与我作斗争,我觉得应该有更好的方法。请尝试演示,如果可以应用任何修复以使其正常工作,那就太好了!

非常感谢任何可以对此进行研究的人,希望如果我们可以使代码正常工作,它将对其他尝试对带有标题的列表进行相同优化的人有用。

4

1 回答 1

5

上述方法的主要问题是数据集是直接从超类执行的代码中更改的。通过填充 getView() 中的标题,我正在更改可能是操作中间的数据或超类中的“不安全”状态。这就是为什么在快速滚动期间调用 onNotifyDataSetChanged 时所有触摸交互都会中断的原因。数据需要从外部源添加,而不是从适配器方法中添加。

这可以通过使用 Handler 或 AsyncTask 将标题添加到适配器并调用其 notifyDataSetChanged 来完成。这些将在 UI 线程上运行,并且不会干扰超类状态。感谢 CommonsWare 的 EndlessAdapter 示例引入了 AsyncTask 的概念以将数据添加到列表中。

进行该更改后,不再需要 onTouchEvent() 和 layoutChildren() hack。

于 2010-12-31T18:54:02.843 回答