5

概要:カスタム アダプター ラッパーを介して ListView に見出し行を動的に追加しようとしています。ListView で、スクロール位置の同期を維持するのに問題があります。実行可能なデモ プロジェクトが提供されます。

CursorAdapter の値に基づいてリストにアイテムを動的に追加したいと思います。ユーザーが現在表示している位置よりも数個先です。これを行うために、CursorAdapter をラップし、新しいコンテンツのインデックスを SparseArray に保持するアダプターがあります。項目がカスタム アダプターに追加されたときに ListView を更新する必要がありますが、それを機能させようとして多くの落とし穴に遭遇したので、アドバイスをいただければ幸いです。

デモ プロジェクトは、 DynamicSectionedList.zipからダウンロードできます。

デモでは、10 か所先を見て、リスト項目が次の文字に切り替わる正しい位置を見つけることによって、見出しが動的に追加されます。notifyDataSetChanged の各実装には、次のような問題があります。

デモ 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 はあらゆる段階で私と戦っているので、より良いアプローチが必要だと感じています。修正を適用して動作させることができる場合は、デモをお試しください。

これを調べることができる人に感謝します。コードを機能させることができれば、見出し付きのリストに対して同じ最適化を達成しようとしている他の人にとって役立つことを願っています。

4

1 に答える 1

5

上記のアプローチの主な問題は、データ セットがスーパークラスによって実行されるコード内から直接変更されていたことです。getView() に見出しを入力することで、操作の途中またはスーパークラスの「安全でない」状態でデータを変更していました。これが、フリング スクロール中に onNotifyDataSetChanged が呼び出されたときに、すべてのタッチ操作が壊れた理由です。データは、アダプター メソッド内からではなく、外部ソースから追​​加する必要があります。

これは、Handler または AsyncTask を使用してヘッダーをアダプターに追加し、notifyDataSetChanged を呼び出すことによって実行できます。これらは UI スレッドで実行され、スーパークラスの状態に干渉しません。リストにデータを追加するための AsyncTask の概念を導入した CommonsWare の EndlessAdapter の例に感謝します。

その変更を行った後、 onTouchEvent() および layoutChildren() ハックは不要になりました。

于 2010-12-31T18:54:02.843 に答える