kamulog

xamarin.formsのネタなど

Xamarin.Forms Guides「ListView Performance」の日本語訳(の練習)

ListView Performance - Xamarin

2018年1月16日時点の翻訳です。

注意事項

  • 基本的に直訳で意訳はあまりしません。
  • あくまで英語のReadingの練習であり、嘘率は高いです。

ListView Performance

Ensure great performance with your ListView-based app.

ListViewを基本としたアプリで優れたパフォーマンスを確保する。

When writing mobile applications, performance matters. Users have come to expect smooth scrolling and fast load times. Failing to meet your users' expectations will cost you ratings in the application store, or in the case of a line-of-business application, cost your organization time and money.

モバイルアプリケーションを書く時はパフォーマンスが重要です。ユーザーはスムーズなスクロールと高速なロード時間を期待するようになってきています。ユーザの期待に応えることに失敗するとアプリケーションのストアでの評価を失い、また業務アプリの場合は組織の時間と費用を失うことになります。

Although ListView is a powerful view for displaying data, it has some limitations. Scrolling performance can suffer when using custom cells, especially when they contain deeply nested view hierarchies or use certain layouts that require a lot of measurement. Fortunately, there are techniques you can use to avoid poor performance.

ListViewはデータを表示するためのパワフルなViewですが、いくつかの制限があります。スクロールのパフォーマンスはカスタムセルを使うとき(特にそれらが深い入れ子の階層のViewを含む場合、または多くの計測を要求するLayoutを使う場合)に悩まされることがあります。幸いなことに、パフォーマンス低下を回避するために使えるテクニックがあります。

The following topics are discussed in this article:

この記事では以下のトピックを説明します。

Caching Strategy

ListViews are often used to display much more data than can fit onscreen. Consider a music app, for example. A library of songs may have thousands of entries. The simple approach, which would be to create a row for every song, would have poor performance. That approach wastes valuable memory and can slow scrolling to a crawl. Another approach is to create and destroy rows as data is scrolled into view. This requires constant instantiation and cleanup of view objects, which can be very slow.

ListViewは画面サイズ以上の大量のデータを表示するためによく使われます。 例えば音楽アプリを考えてみてください。音楽ライブラリは何千もの曲を持つかもしれません。簡単なアプローチでは、曲ごとに1つの行を生成することになり、パフォーマンスを低下させるでしょう。そのアプローチでは貴重なメモリを浪費し、画面のスクロールを遅くする可能性があります。別のアプローチは表示領域にデータがスクロールされた時に行の生成と破棄をすることです。この方法は絶えず表示オブジェクトのインスタンス化とクリーンアップを要求し、その処理はとても重くなる可能性があります。

In order to conserve memory, the native ListView equivalents for each platform have built-in features for re-using rows. Only the cells visible on screen are loaded in memory and the content is loaded into existing cells. This prevents the application from needing to instantiate thousands of objects, saving time and memory.

モリーを節約するために、各プラットフォームのnativeのListViewに相当するものには行を再利用するための組み込みの機能があります。画面上に見えているセルだけがメモリーにロードされ、そのセルに内容が読み込まれます。この機能によってアプリケーションは何千ものオブジェクトのインスタンス化の要求を防ぐことができ、時間とメモリーを節約できます。

Xamarin.Forms permits ListView cell re-use through the ListViewCachingStrategy enumeration, which has the following values:

Xamarin.FormsはListViewCachingStrategyというEnum型を使ってListViewのセルの再利用を可能にします。そしてそれらは以下のような値を持ちます。

public enum ListViewCachingStrategy
{
  RetainElement,   // the default value
  RecycleElement,
  RecycleElementAndDataTemplate
}

Note: The Universal Windows Platform (UWP) ignores the RetainElement caching strategy, because it always uses caching to improve performance. Therefore, by default it behaves as if the RecycleElement caching strategy is applied.

注意:UWPでは常にパフォーマンス向上のためにキャッシュ機能を使うため、CachingStrategyのRetainElementは無視されます。したがってデフォルトでRecycleElementが適用されているように振る舞います。

RetainElement

The RetainElement caching strategy specifies that the ListView will generate a cell for each item in the list, and is the default ListView behavior. It should generally be used in the following circumstances:

Cahing strategyのRetainElementはListViewがリストの各要素ごとに1つのセルを生成することを指定し、これがデフォルトのListViewの振る舞いです。一般的には次のような状況での使用が推奨されます。

  • When each cell has a large number of bindings (20-30+).
  • When the cell template changes frequently.
  • When testing reveals that the RecycleElement caching strategy results in a reduced execution speed.
  • 各セルが多くのbindingを持つとき(20〜30以上)
  • セルのtemplateが頻繁に変更されるとき
  • テストでRecycleElementが実行速度を下げる結果になることが判明したとき

It's important to recognize the consequences of the RetainElement caching strategy when working with custom cells. Any cell initialization code will need to run for each cell creation, which may be multiple times per second. In this circumstance, layout techniques that were fine on a page, like using multiple nested StackLayout instances, become performance bottlenecks when they are setup and destroyed in real time as the user scrolls.

カスタムセルを使って動かす場合は、RetainElementでの結果を認識することが大切です。どんなセルの初期化コードでも各セルの生成ごとに走らせる必要があり、そしてそれは1秒間に複数回になるかもしれません。その状況では、ページでの凝ったLayoutテクニック(複数のネストされたStackLayoutを使っているような場合)は、それらがスクロールの際にリアルタイムで生成され破棄されるときに、パフォーマンスのボトルネックになります。

RecycleElement

The RecycleElement caching strategy specifies that the ListView will attempt to minimize its memory footprint and execution speed by recycling list cells. This mode does not always offer a performance improvement, and testing should be performed to determine any improvements. However, it is generally the preferred choice, and should be used in the following circumstances:

Caching strategyのRecycleElementを指定すると、ListViewはリストのセルを再利用することによって、メモリ使用量と実行速度を最小にすることを試みます。このモードは常にパフォーマンスの向上を提供するわけではなく、どのような改善があるかを判断するためにはテストを実施する必要があります。しかし、それ(RecycleElement)は一般的に好ましい選択であり、次のような状況での使用が推奨されます。

  • When each cell has a small to moderate number of bindings.
  • When each cell's BindingContext defines all of the cell data.
  • When each cell is largely similar, with the cell template unchanging.
  • 各セルがbindingの数が小〜中程度の場合
  • 各セルのBindingContextが全てのセルデータを定義している場合
  • 各セルがだいたい同じで、セルのtemplateが変化しない場合

During virtualization the cell will have its binding context updated, and so if an application uses this mode it must ensure that binding context updates are handled appropriately. All data about the cell must come from the binding context or consistency errors may occur. This can be accomplished by using data binding to display cell data. Alternatively, cell data should be set in the OnBindingContextChanged override, rather than in the custom cell's constructor, as demonstrated in the following code example:

仮想化の間はセルは自身のbinding contextが更新されるので、アプリがこのモードを使う場合はbinding contextの更新が適切に処理されることを保証しなければなりません。セルに関するすべてのデータはそのbinding contextに由来するものでなければなりません。そうしないと整合性エラーが発生する可能性があります。この処理は、セルのデータの表示にdata bindingを使うことによって実現できます。あるいは、以下のコードサンプルで示すように、セルのデータをカスタムセルのコンストラクタの中ではなく、OnBindingContextChangedのoverrideの中でセットするようにしても良いでしょう。

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;
    }
  }
}

For more information, see Binding Context Changes.

より詳しい情報はBinding Context Changesのページを参照してください。

On iOS and Android, if cells use custom renderers, they must ensure that property change notification is correctly implemented. When cells are reused their property values will change when the binding context is updated to that of an available cell, with PropertyChanged events being raised. For more information, see Customizing a ViewCell.

iOSAndroidで、セルにCustomRendererを使用している場合は、プロパティ更新通知が正しく実装されていることを保証しなくてはなりません。セルが再利用されるとき、プロパティの値は、binding contextが次の有効なセルのものに更新されるときにPropertyChangedイベントの発生をともなって変更されます。より詳しい情報はCustomizing a ViewCellを参照してください。

RecycleElement with a DataTemplateSelector

When a ListView uses a DataTemplateSelector to select a DataTemplate, the RecycleElement caching strategy does not cache DataTemplates. Instead, a DataTemplate is selected for each item of data in the list.

ListViewがDataTemplateを選択するためにDataTemplateSelectorを使用する場合、Caching strategy RecycleElementはDataTemplateをキャッシュしません。その代わり、DataTemplateはリストのデータの要素ごとに選択されます。

Note: The RecycleElement caching strategy has a pre-requisite, introduced in Xamarin.Forms 2.4, that when a DataTemplateSelector is asked to select a DataTemplate that each DataTemplate must return the same ViewCell type. For example, given a ListView with a DataTemplateSelector that can return either MyDataTemplateA (where MyDataTemplateA returns a ViewCell of type MyViewCellA), or MyDataTemplateB (where MyDataTemplateB returns a ViewCell of type MyViewCellB), when MyDataTemplateA is returned it must return MyViewCellA or an exception will be thrown.

注意:RecycleElementにはXamarin.Forms 2.4で導入された前提条件があります。それはDataTemplateSelectorがDataTemplateを選択することを要求されると、それぞれのDataTemplateは同じViewCellの型を返さなければならないということです。例えば、MyDataTemplateA(MyViewCellAという型のViewCellを返す)またはMyDataTemplateB(MyViewCellBという型のViewCellを返す)のどちらも返す可能性があるDataTemplateSelectorを持つListViewがあった場合、MyDataTemplateAが返されたときはMyViewCellAが返りますが、それ以外は例外がスローされるようになります。

(例えが分かりにくいけど、多分この例だとダメですよってことだと思う)

RecycleElementAndDataTemplate

The RecycleElementAndDataTemplate caching strategy builds on the RecycleElement caching strategy by additionally ensuring that when a ListView uses a DataTemplateSelector to select a DataTemplate, DataTemplates are cached by the type of item in the list. Therefore, DataTemplates are selected once per item type, instead of once per item instance.

caching strategyのRecycleElementAndDataTemplateはRecycleElementを拡張したもので、ListViewがDataTemplateを選択するためにDataTemplateSelectorを使う時に、DataTemplateをリストの要素の型ごとにキャッシュする機能を加えたものです。これによりDataTemplateは、要素のインスタンスごとに1つではなく、要素の型ごとに1つ選択されるようになります。

Note: The RecycleElementAndDataTemplate caching strategy has a pre-requisite that the DataTemplates returned by the DataTemplateSelector must use the DataTemplate constructor that takes a Type.

注意:RecycleElementAndDataTemplateは前提条件があり、DataTemplateSelectorによって返されるDataTemplateはType型を引数にとるDataTemplateのコンストラクタを使わなければなりません。

Setting the Caching strategy

The ListViewCachingStrategy enumeration value is specified with a ListView constructor overload, as shown in the following code example:

ListViewCachingStrategyのenum型の値は次のコード例で示すようにListViewのコンストラクタのoverloadで指定します。

var listView = new ListView(ListViewCachingStrategy.RecycleElement);

In XAML, set the CachingStrategy attribute as shown in the code below:

Xamlでは以下のコードで示すようにCachingStrategy属性をセットします。

<ListView CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
              ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

This has the same effect as setting the caching strategy argument in the constructor in C#; note that there is no CachingStrategy property on ListView.

これはC#でコンストラクタでcaching strategyをセットするのと同じ効果があります。ただしListViewにはCachingStrategyというプロパティは存在しないことに注意してください。

Setting the Caching Strategy in a Subclassed ListView

Setting the CachingStrategy attribute from XAML on a subclassed ListView will not produce the desired behavior, because there is no CachingStrategy property on ListView. In addition, if XAMLC is enabled, the following error message will be produced: No property, bindable property, or event found for 'CachingStrategy'

ListViewのサブクラスではXamlでのCachingStrategy属性の設定は、期待した動作は起こりません。ListViewにはCachingstrategyというプロパティは存在しないからです。さらに、XamlCが有効であれば、次のようなエラーメッセージが表示されるでしょう。
No property, bindable property, or event found for 'CachingStrategy'

The solution to this issue is to specify a constructor on the subclassed ListView that accepts a ListViewCachingStrategy parameter and passes it into the base class:

この問題の解決方法は、ListViewCachingStrategyパラメータを受け取るListViewのサブクラスのコンストラクタを設定し、それを基底クラスに渡すようにすることです。

public class CustomListView : ListView
{
  public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
  {
  }
  ...
}

Then the ListViewCachingStrategy enumeration value can be specified from XAML by using the x:Arguments syntax:

そしてListViewCachingStrategyの値はXamlからはx:Argumens構文を使うことで指定できます。

<local:CustomListView>
  <x:Arguments>
    <ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
  </x:Arguments>
</local:CustomListView>

Improving ListView Performance

There are many techniques for improving the performance of a ListView:

ListViewのパフォーマンス向上のための多くのテクニックがあります。

  • Bind the ItemsSource property to an IList<T> collection instead of an IEnumerable<T> collection, because IEnumerable<T> collections don't support random access.
  • Use the built-in cells (like TextCell / SwitchCell ) instead of ViewCell whenever you can.
  • Use fewer elements. For example consider using a single FormattedString label instead of multiple labels.
  • Replace the ListView with a TableView when displaying non-homogenous data – that is, data of different types.
  • Limit the use of the Cell.ForceUpdateSize method. If overused, it will degrade performance.
  • On Android, avoid setting a ListView's row separator visibility or color after it has been instantiated, as it results in a large performance penalty.
  • Avoid changing the cell layout based on the BindingContext. This incurs large layout and initialization costs.
  • Avoid deeply nested layout hierarchies. Use AbsoluteLayout or Grid to help reduce nesting.
  • Avoid specific LayoutOptions other than Fill (Fill is the cheapest to compute).
  • Avoid placing a ListView inside a ScrollView for the following reasons:
    • The ListView implements its own scrolling.
    • The ListView will not receive any gestures, as they will be handled by the parent ScrollView.
    • The ListView can present a customized header and footer that scrolls with the elements of the list, potentially offering the functionality that the ScrollView was used for. For more information see Headers and Footers.
  • Consider a custom renderer if you need a very specific, complex design presented in your cells.
  • ItemsSourceにはIEnumerable<T>ではなくIList<T>をBindする。IEnumerable<T>のコレクションはランダムアクセスをサポートしていない。
  • できる限りViewCellではなく組み込みセル(TextCell / SwitchCell等)を使う。
  • より少ない要素を使う。例えば複数のLabelの代わりに文字列を整形して1つのLabelを使うことを検討する。
  • 不均一のデータ(異なる型のデータ)を表示する時はListViewをTableViewに置き換える。
  • CellのForceUpdateSizeメソッドの使用を控える。使いすぎるとパフォーマンスを低下させる。
  • Androidで、インスタンス化した後でListViewのrow separatorのvisibillityやcolorを設定することを避ける。これは大きなパフォーマンスのペナルティをもたらす。
  • BindingContextに基づいたセルのレイアウト変更を避ける。これは大きなレイアウトと初期化のコストが発生する。
  • 深くネストした階層のレイアウトは避ける。ネストを減らすためにGridかAbsoluteLayoutを使う。
  • LayoutOptionにFill(もっとも計算量が低い)以外を指定することを避ける。
  • ScrollViewの内部にListViewを配置することは以下の理由で避ける。
    • ListViewには自身にスクロール機能が実装されている
    • ListViewはリストの要素と同時にスクロールするカスタマイズされたヘッダーとフッターを表示することができ、潜在的にその機能のためにScrollViewの使用が提供される(意味不明で申し訳ない)。より詳しい情報はHeaders and Footersを参照してください。
  • もしセルの中で非常に特殊な複雑なデザインが必要な場合はCustomRendererを検討する。

AbsoluteLayout has the potential to perform layouts without a single measure call. This makes it very powerful for performance. If AbsoluteLayout cannot be used, consider RelativeLayout. If using RelativeLayout, passing Constraints directly will be considerably faster than using the expression API. That is because the expression API uses JIT, and on iOS the tree has to be interpreted, which is slower. The expression API is suitable for page layouts where it only required on initial layout and rotation, but in ListView, where it's run constantly during scrolling, it hurts performance.

AbsoluteLayoutは1回の計測呼び出しもなしでレイアウトを実行できる可能性があります。これはパフォーマンス上とても強力です。もしAbsoluteLayoutが使えないのであれば、RelativeLayoutを検討してください。RelativeLayoutを使えば、直接制約を渡すことで式木APIを使うよりもかなり速くなるでしょう。それは式木APIJITを使い、iOSでは(実行時に)そのツリーが解釈され、それがとても遅いからです。式木APIはレイアウトの初期化や回転時にだけ呼ばれるようなページレイアウトに適しています。しかしListViewではそれ(レイアウトの初期化)はスクロールの間、絶え間なく走り、パフォーマンスを損ないます。

Building a custom renderer for a ListView or its cells is one approach to reducing the effect of layout calculations on scrolling performance. For more information, see Customizing a ListView and Customizing a ViewCell.

ListViewやCellのCustomRendererを作ることは、スクロール実行時のレイアウト計算の処理を減らす1つのアプローチです。より詳しい情報はCustomizing a ListViewCustomizing a ViewCellを参照してください。

まとめ

RecycleElementは変動するデータは全てBindingを使ってセットすることが必要で、Bindingを使わないのであればViewCellのサブクラスを作ってOnBindingContextChangedのoverrideに更新処理を記述する必要がある…という風に理解したけど、XamlでBindingしとくだけで本当に大丈夫かは試してないので不明です。

またCustomRendererでNativeCellを使うのであれば、Forms側のレイアウトのコストは無いので、Retainのままでもあまり問題はなさそう。相当大量のデータの場合はRecycleにした方が良いと思いますが、その場合は当然全項目のPropertyChangedでの更新が必須になってかなりダルいことが予想されます。

RecycleElementAndDataTemplateなんていつの間に出来たんでしょうか。DataTemplateSelectorを使う場合はこれを指定した方が良さそうですね。

一応補足ですが、ここで述べられているリサイクルとはForms側のViewのリサイクルのことで、Native側ではRetainだろうが何であろうが、セルの再利用は動作しています。

次回候補