動的アダプターラッパー Android での NoticeDataSetChanged/ListView の問題を修正する方法
-
14-10-2019 - |
質問
まとめ: カスタム アダプター ラッパーを介して ListView に見出し行を動的に追加しようとしています。ListView でスクロール位置の同期を維持するのに問題が発生しています。実行可能なデモプロジェクトが提供されています。
CursorAdapter の値に基づいて、ユーザーが現在表示しているものの数個前の位置に項目をリストに動的に追加したいと考えています。これを行うために、CursorAdapter をラップし、新しいコンテンツのインデックスを SparseArray に保持するアダプターを用意しました。カスタム アダプターに項目が追加されると ListView を更新する必要がありますが、それを機能させるために多くの落とし穴に遭遇したため、アドバイスが欲しいです。
デモ プロジェクトはここからダウンロードできます。 DynamicSectionedList.zip
デモでは、リスト項目が次の文字に切り替わる正しい位置を見つけるために 10 桁先を見て、見出しが動的に追加されます。NoticeDataSetChanged の各実装には、次のような問題があります。
デモ1このデモでは、notifyDataSetChanged() の重要性を示します。何かをクリックすると、アプリがクラッシュします。これは、ListView での健全性チェックが原因です。 mItemCount != adapter.getItemCount()
. 。道徳的には、データが変更されたことをリストに通知する必要があります。
デモ2自然な次のステップは、変更が発生したときに ListView に変更を通知することです。残念ながら、ListView のスクロール中にこれを行うと、アプリがタッチ モードから切り替わるまで、すべてのタッチ インタラクションが完全に中断されます。これに気づくには、新しい見出しを生成するのに十分な距離まで「スクロール」する必要があります。画面をタップしてもスクロールは停止せず、停止するとリスト項目はいずれもクリックできなくなります。これはいくつかの原因によるものです if (!mDataChanged) { /* do very important stuff */ }
AbsListView.onTouchEvent() のコード。
デモ3これを修正するために、デモ 3 では pendingChanges フラグを導入し、カスタム アダプターは、変更に対して「安全な」状態になった後に ListView によって呼び出すことができる、notifyDataSetChangedIfNeeded() を取得します。変更を通知する必要がある最初のポイントは ListView.layoutChildren() 内にあるため、必要に応じて最初に変更を通知し、その後呼び出しを行うようにそのメソッドをオーバーライドしました。少なくとも 1 つの見出しを通過して、リスト項目をクリックします。
これはまったく正しく機能しませんが、その理由は完全にはわかりません。キーボード/トラックボールで項目をタップまたは選択すると、古い位置が適切に同期されずにリストが更新されます。リストの一番上までスクロールしますが、これは受け入れられません。
デモ4デモ 3 のスクロールの問題は、少なくともタッチ モードでは克服できます。タッチダウン時にnotifyDataSetChangedIfNeeded()への呼び出しを追加することで、すべてのタッチ操作が期待どおりに機能し、リストの位置が適切に同期されるようなタイミングでデータ変更が行われるようになります。
ただし、デバイスがタッチモードではない場合、それがハッキングであると思われることは言うまでもなく、そのアナログが見つかりません。リストはほとんど常に一番上までスクロールして戻りますが、時折正しい位置を維持する原因がわかりません。
Android は各段階で私と戦っているので、もっと良いアプローチがあるべきだと感じています。デモを試してみてください。修正を適用して動作させることができれば、それは素晴らしいことです。
これを調べていただける方に感謝します。コードを機能させることができれば、見出し付きのリストに対して同じ最適化を達成しようとしている他の人にとって役立つことを願っています。
解決
上記のアプローチの主な問題は、データ セットがスーパークラスによって実行されるコード内から直接変更されることでした。getView() に見出しを設定することで、操作の途中またはスーパークラスの「安全でない」状態の可能性があるときにデータを変更していました。これが、フリング スクロール中に onNotifyDataSetChanged が呼び出されたときにすべてのタッチ インタラクションが壊れる理由です。データはアダプター メソッド内からではなく、外部ソースから追加する必要があります。
これは、ハンドラーまたは AsyncTask を使用してアダプターにヘッダーを追加し、その NoticeDataSetChanged を呼び出すことで実行できます。これらは UI スレッドで実行され、スーパークラスの状態に干渉しません。データをリストに追加する AsyncTask の概念を導入した CommonsWare の EndlessAdapter サンプルに感謝します。
この変更を加えた後、onTouchEvent() およびlayoutChildren() ハックは必要なくなりました。