ListView のパフォーマンス
モバイル アプリケーションを作成する際、重要になるのがパフォーマンスです。 ユーザーは、スムーズはスクロールと高速な読み込み時間を期待するようになりました。 ユーザーの期待に応えることができなければ、アプリケーション ストアでの評価が低下します。また、基幹業務アプリケーションの場合は、時間と費用の面で、組織に負担をかけることになります。
Xamarin.FormsListView
は、データを表示するための強力なビューですが、いくつかの制限があります。 カスタム セルを使用する場合、特に深く入れ子になったビュー階層が含まれている場合や、複雑な測定を必要とする特定のレイアウトを使用する場合、スクロールのパフォーマンスが低下する場合があります。 ただし、パフォーマンス低下を回避するために使用できる手法があります。
キャッシュ戦略
ListView は、多くの場合、画面上に収まる量よりもはるかに多くのデータを表示するために使用されます。 たとえば、音楽アプリには、何千ものエントリを含む曲のライブラリがある場合があります。 すべてのエントリに対して項目を作成すると、貴重なメモリが無駄になり、パフォーマンスが低下します。 行の作成と破棄を継続的に行うには、アプリケーションでオブジェクトのインスタンスの作成とクリーンアップを継続的に行う必要があり、これにより、パフォーマンスも低下します。
メモリを節約するために、各プラットフォームのネイティブ ListView
の同等物には、行を再利用するための組み込み機能があります。 画面上に表示されるセルのみがメモリに読み込まれ、コンテンツが既存のセルに読み込まれます。 このパターンにより、アプリケーションで何千ものオブジェクトのインスタンスが作成されるのを防ぐことができ、時間とメモリが節約されます。
Xamarin.Forms では、次の値を持つ ListViewCachingStrategy
列挙体を使用して ListView
セルを再利用できます。
public enum ListViewCachingStrategy
{
RetainElement, // the default value
RecycleElement,
RecycleElementAndDataTemplate
}
Note
ユニバーサル Windows プラットフォーム (UWP) では、パフォーマンスを向上するために常にキャッシュが使用されるため、RetainElement
キャッシュ戦略は無視されます。 このため、既定では、RecycleElement
キャッシュ戦略が適用されている場合と同様に動作します。
RetainElement
RetainElement
キャッシュ戦略では、ListView
がリスト内の各項目のセルを生成することを指定し、これが既定の ListView
動作になります。 これは、次の状況で使用する必要があります。
- 各セルには、多数のバインド (20 ~ 30 以上) があります。
- セル テンプレートは頻繁に変更されます。
- テストでは、
RecycleElement
キャッシュ戦略によって実行速度が低下することが明らかになります。
カスタム セルを使用する場合は、RetainElement
キャッシュ戦略の結果を認識することが重要です。 セルの初期化コードは、セルの作成ごとに実行する必要があります。1 秒あたりに複数回実行できます。 この状況では、複数の入れ子になった StackLayout
インスタンスの使用など、ページ上では問題がなかったレイアウト手法でも、ユーザーがスクロールするときに設定や破棄がリアルタイムで行われると、パフォーマンスのボトルネックになります。
RecycleElement
RecycleElement
キャッシング戦略では、ListView
がメモリ占有領域と実行速度を最小限に抑えるためリストのセルをリサイクルするよう指定します。 このモードでは常にパフォーマンスが向上するとは限りません。テストを実行して改善点を特定する必要があります。 ただし、これは推奨される選択肢であり、次の状況で使用する必要があります。
- 各セルには、少数から中程度の数のバインドがあります。
- 各セルの
BindingContext
は、すべてのセル データを定義します。 - 各セルは大きく似ていますが、セル テンプレートは変更されません。
仮想化中、セルのバインディング コンテキストが更新されるため、アプリケーションでこのモードを使用する場合、バインディング コンテキストの更新が適切に処理されることを保証する必要があります。 セルに関するすべてのデータは、バインディング コンテキストから取得する必要があります。または、整合性エラーが発生する可能性があります。 この問題は、データ バインディングを使用してセル データを表示することで回避できます。 または、次のコード例に示すように、セル データをカスタム セルのコンストラクターではなく、OnBindingContextChanged
のオーバーライドで設定する必要があります。
public class CustomCell : ViewCell
{
Image image = null;
public CustomCell ()
{
image = new Image();
View = image;
}
protected override void OnBindingContextChanged ()
{
base.OnBindingContextChanged ();
var item = BindingContext as ImageItem;
if (item != null) {
image.Source = item.ImageUrl;
}
}
}
詳細については、「バインディング コンテキストの変更」を参照してください。
iOS および Android では、セルでカスタム レンダラーを使用する場合、プロパティ変更通知が正しく実装されていることを保証する必要があります。 セルが再利用される場合、バインディング コンテキストが使用可能なセルのコンテキストに更新されると、セルのプロパティ値が変更され、PropertyChanged
イベントが発生します。 詳細については、「ViewCell のカスタマイズ」を参照してください。
DataTemplateSelector を使用した RecycleElement
ListView
で DataTemplateSelector
を使用して DataTemplate
を選択する場合、RecycleElement
キャッシュ戦略によって DataTemplate
はキャッシュされません。 代わりに、リスト内のデータの各項目に対して DataTemplate
が選択されます。
Note
RecycleElement
キャッシュ戦略には、Xamarin.Forms 2.4 で導入された前提条件があり、DataTemplateSelector
が DataTemplate
の選択を求められた場合、各 DataTemplate
は同じ ViewCell
型を返す必要があります。 たとえば、MyDataTemplateA
(MyDataTemplateA
は MyViewCellA
型の ViewCell
を返す) または MyDataTemplateB
(MyDataTemplateB
は MyViewCellB
型の ViewCell
を返す) を返す DataTemplateSelector
で ListView
が指定された場合、MyDataTemplateA
が返されたときは MyViewCellA
を返す必要があり、そうでない場合は例外がスローされます。
RecycleElementAndDataTemplate
RecycleElementAndDataTemplate
キャッシュ戦略は、RecycleElement
キャッシュ戦略に基づいて構築されており、ListView
で DataTemplateSelector
を使用して DataTemplate
を選択するときに、リスト内の項目の種類ごとに DataTemplate
が確実にキャッシュされるようにします。 したがって、DataTemplate
は、項目インスタンスごとに 1 回ではなく、項目の種類ごとに 1 回選択されます。
Note
RecycleElementAndDataTemplate
キャッシュ戦略には、DataTemplateSelector
で返される DataTemplate
は、Type
を受け取る DataTemplate
コンストラクターを使用する必要があるという前提条件があります。
キャッシュ戦略の設定
次のコード例に示すように、ListViewCachingStrategy
列挙値は ListView
コンストラクター オーバーロードで指定されます。
var listView = new ListView(ListViewCachingStrategy.RecycleElement);
XAML で、次の XAML に示すように CachingStrategy
属性を設定します。
<ListView CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
このメソッドには、C# のコンストラクターでキャッシュ戦略引数を設定する場合と同じ効果があります。
サブクラス化された ListView でキャッシュ戦略を設定する
サブクラス化された ListView
で XAML から CachingStrategy
属性を設定しても、ListView
に CachingStrategy
プロパティがないため、目的の動作は生成されません。 さらに、XAMLC を有効にすると、'CachingStrategy' のプロパティ、バインド可能なプロパティ、またはイベントが見つかりませんというエラー メッセージが生成されます
この問題を解決するには、サブクラス化された ListView
で、ListViewCachingStrategy
パラメーターを受け取り、それを基底クラスに渡すコンストラクターを指定します。
public class CustomListView : ListView
{
public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
{
}
...
}
この後、x:Arguments
構文を使用して、XAML から ListViewCachingStrategy
列挙値を指定することができます。
<local:CustomListView>
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</local:CustomListView>
ListView のパフォーマンスに関する推奨事項
ListView
のパフォーマンスを向上するには、多くの手法があります。 次の推奨事項は、ListView のパフォーマンスを向上する可能性があります
IEnumerable<T>
コレクションではランダム アクセスがサポートされないため、ItemsSource
プロパティをIEnumerable<T>
コレクションではなくIList<T>
コレクションにバインドします。- 可能な限り、
ViewCell
ではなく組み込みのセル (TextCell
、SwitchCell
など) を使用します。 / - 使用する要素の数を減らします。 たとえば、複数のラベルではなく 1 つの
FormattedString
ラベルを使用することを検討してください。 - 異種データ、つまり異なる型のデータを表示する場合は、
ListView
をTableView
に置き換えます。 Cell.ForceUpdateSize
メソッドの使用を制限します。 過度に使用すると、パフォーマンスが低下します。- Android では、インスタンスの作成後に
ListView
の行区切りの表示や色を設定しないようにします。これにより、パフォーマンスが大幅に低下するためです。 BindingContext
に基づいてセルのレイアウトを変更しないようにします。 レイアウトを変更すると、測定と初期化に多額のコストがかかります。- 深く入れ子になったレイアウト階層を使用しないようにします。 入れ子を減らすには、
AbsoluteLayout
またはGrid
を使用します。 Fill
以外の特定のLayoutOptions
を使用しないようにします (Fill
は、コンピューティング コストが最も安価です)。- 次の理由により、
ScrollView
内にListView
を配置しないようにします。ListView
は独自のスクロールを実装します。ListView
はジェスチャーを受け取りませ。ジェスチャーは親ScrollView
によって処理されます。ListView
リストの要素と共にスクロールするカスタマイズされたヘッダーとフッターを表示できるため、ScrollView
で使用されていた機能を提供できる可能性があります。 詳細については、「ーとフッター照してください。
- セル内に特定の複雑なデザインを表示する必要がある場合は、カスタム レンダラーを検討してください。
AbsoluteLayout
、1 回の測定呼び出しを行わずにレイアウトを実行できるため、パフォーマンスが高くなる可能性があります。 AbsoluteLayout
を使用できない場合は、RelativeLayout
を検討してください。 RelativeLayout
を使用する場合、Constraints を直接渡すと、式 API を使用するよりもかなり高速になります。 式 API は JIT を使用し、さらに iOS では、ツリーを解釈する必要があり、速度が低下するため、このメソッドの方が高速です。 式 API は、初期レイアウトとローテーションでのみ必要となるページ レイアウトには適していますが、ListView
では、式 API がスクロール中常に実行されるため、パフォーマンスが低下します。
ListView
またはそのセル用のカスタム レンダラーを構築することは、スクロール パフォーマンスに対するレイアウト計算の影響を軽減するための 1 つの方法です。 詳細については、「ListView のカスタマイズ」および「ViewCell のカスタマイズ」を参照してください。