如何修复动态适配器包装器 Android 中的 notificationDataSetChanged/ListView 问题

发布于 2024-10-09 11:29:10 字数 1636 浏览 7 评论 0原文

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

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

演示项目可以在此处下载: DynamicSectionedList.zip

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

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

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

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

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

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

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

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

非常感谢任何能够研究这个问题的人,希望如果我们能让代码正常工作,这对于其他试图对带有标题的列表完成相同优化的人来说将是有用的。

Summary: Trying to dynamically add heading rows to a ListView via a custom adapter wrapper. ListView is having trouble keeping the scroll position in sync. Runnable demo project provided.

I would like to dynamically add items to a list based on the values in a CursorAdapter, several positions ahead of what the user is currently viewing. To do this, I have an adapter that wraps the CursorAdapter and keeps the new content indexed in a SparseArray. The ListView needs to be updated when items are added to the custom adapter, but I have met a lot of pitfalls trying to get that to work and would love some advice.

The demo project can be downloaded here: DynamicSectionedList.zip

In the demo, the headings are added dynamically by looking ahead 10 places to find the correct position where the list items switch to the next letter. Each implementation of notifyDataSetChanged has problems as described:

Demo 1
This demo shows the importance of notifyDataSetChanged(). On clicking anything, the app will crash. This is due to some sanity checking in ListView... mItemCount != adapter.getItemCount(). Moral is, we need to notify the list that data has changed.

Demo 2
The natural next step is to notify the ListView of changes when changes occur. Unfortunately, doing so while the ListView is scrolling firmly breaks all touch interaction until the app switches out of touch mode. You will need to "fling scroll" far enough to generate new headings in order to notice this. Tapping the screen will not cause the scroll to stop, and once stopped none of the list items will be clickable. This is due to some if (!mDataChanged) { /* do very important stuff */ } code in AbsListView.onTouchEvent().

Demo 3
To fix this, Demo 3 introduces a pendingChanges flag and the custom Adapter gains a notifyDataSetChangedIfNeeded() which can be called by the ListView once it has entered a "safe" state for changes. The first point where changes must be notified is in ListView.layoutChildren(), so I overrode that method to first notify of changes if needed, then call through. Fling past at least one heading then click a list item.

This doesn't quite work right, though I'm not totally sure why. Tapping or selecting an item with the keyboard/trackball causes the list to refresh without properly syncing the old position. It scrolls to the top of the list which is not acceptable.

Demo 4
The scroll problem in Demo 3 can be conquered, at least in touch mode. By adding a call to notifyDataSetChangedIfNeeded() on touch down, the data change happens to take place at such a time that all touch interaction works as expected and the list position is properly synced.

However, I can't find an analog for that when the device is not in touch mode, not to mention the fact that it definitely seems like a hack. The list almost always scrolls back to the top, I can't find out what causes it to occasionally maintain the correct position.

Since Android is fighting me at each step of the way, I feel like there should be a better approach. Please try the demo, if any fixes can be applied to get it working that would be great!

Many thanks to anyone who can look into this, hopefully if we can get the code working it will be useful for others trying to accomplish the same optimization for lists with headings.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

苏辞 2024-10-16 11:29:10

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

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

进行此更改后,不再需要 onTouchEvent() 和 layoutChildren() 黑客。

The main problem with the approach presented above was that the data set was being changed directly from within code executed by the superclasses. By populating the headings in getView() I was changing the data in what could be the middle of an operation or an "unsafe" state in the superclasses. This is why all touch interaction broke when onNotifyDataSetChanged was called during a fling scroll. The data needs to be added from an external source, not from within the adapter methods.

That can be done by using a Handler or AsyncTask to add the headings to the adapter and call its notifyDataSetChanged. These will be run on a UI thread and will not interfere with the superclass state. Thanks to CommonsWare's EndlessAdapter example for introducing the concept of an AsyncTask to add data to a list.

The onTouchEvent() and layoutChildren() hacks were no longer needed after making that change.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文