モバイル アプリケーションを作成する際、重要になるのがパフォーマンスです。 ユーザーは、スムーズはスクロールと高速な読み込み時間を期待するようになりました。 ユーザーの期待に応えることができなければ、アプリケーション ストアでの評価が低下します。また、基幹業務アプリケーションの場合は、時間と費用の面で、組織に負担をかけることになります。
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 のカスタマイズ」を参照してください。