次の方法で共有


第 26 章の概要: カスタム レイアウト

Note

この本は 2016 年春に発行されて以降、改訂されていません。 多くの情報はまだ価値がありますが、一部の資料は古くなっており、トピックの中にはまったく正しくないものまたは不完全なものもあります。

Xamarin.Forms には、Layout<View> から派生した複数のクラスが含まれます。

  • StackLayout
  • Grid
  • AbsoluteLayout、および
  • RelativeLayout

この章では、Layout<View> から派生する独自のクラスの作成方法について説明します。

レイアウトの概要

Xamarin.Forms レイアウトを処理する集中管理型システムはありません。 それぞれの要素では、必要な固有のサイズと、特定の領域内に自己をレンダリングする方法を決定する必要があります。

親と子

子を持つ要素はすべて、それらの子を要素自体の中に配置する必要があります。 親内で利用可能なサイズとその子に想定されるサイズに基づいて、最終的に子に必要なサイズを決定するのは、親です。

サイズと位置の決定

レイアウトは、そのページでのビジュアル ツリーの一番上から開始され、すべての分岐を通ります。 レイアウトで最も重要なパブリック メソッドは、VisualElement によって定義された Layout です。 他の要素の親になっている要素はすべて、その子それぞれに対して Layout を呼び出して、Rectangle 値の形式で子のサイズと位置を相対的に指定します。 これらの Layout の呼び出しは、ビジュアル ツリーを通じて伝達されます。

Layout の呼び出しは、画面上に要素を表示するために必要であり、呼び出しが行われると、次の読み取り専用プロパティが設定されます。 それらは、メソッドに渡される Rectangle と一致します。

Layout の呼び出し前には、HeightWidth には –1 のモック値が含まれています。

Layout を呼び出すと、次の保護されたメソッドの呼び出しもトリガーされます。

最後に、次のイベントが発生します。

OnSizeAllocated メソッドは、Xamarin.Forms 内に 2 つだけ存在する、子を持つことができるクラスの PageLayout によってオーバーライドされます。 オーバーライドされたメソッドでは、以下を呼び出します

LayoutChildren では、要素の子それぞれに対して Layout を呼び出します。 少なくとも 1 つの子に新しい Bounds 設定がある場合、次のイベントが発生します。

制約とサイズ要求

LayoutChildren では、すべての子に対して Layout を賢く呼び出すために、子にとって "望ましい" または "必要な" サイズを把握している必要があります。 そのため、通常は各子に対する Layout 呼び出しの前に、以下を呼び出します

本書が公開された後に、GetSizeRequest メソッドは非推奨となり、以下に置き換えられました

Measure メソッドは Margin プロパティに対応しており、次の 2 つのメンバーを持つ MeasureFlag 型の引数を含みます。

多くの要素では、GetSizeRequest または Measure によって、そのレンダラーから要素のネイティブ サイズを取得します。 どちらのメソッドにも、幅と高さの "制約" に対応するパラメーターがあります。 たとえば、Label では、幅の制約を使用して、複数行のテキストを折り返す方法を決定します。

GetSizeRequestMeasure では両方とも、SizeRequest 型の値を返します。次の 2 つのプロパティがあります。

多くの場合、これら 2 つの値は同一になり、通常は Minimum 値は無視できます。

また、VisualElement には、GetSizeRequest から呼び出される GetSizeRequest と同様の、以下の保護されたメソッドも定義されています。

該当のメソッドは現在では非推奨となり、以下に置き換えられました。

Layout または Layout<T> から派生するすべてのクラスでは、OnSizeRequest または OnMeasure をオーバーライドする必要があります。 ここでは、レイアウト クラスによって固有のサイズが決定されます。通常は、子に対して GetSizeRequest または Measure を呼び出すことで取得した子のサイズに基づきます。 OnSizeRequest または OnMeasure を呼び出す前後に、GetSizeRequest または Measure では、次のプロパティに基づいて調整を行います。

  • double 型の WidthRequestSizeRequestRequest プロパティに影響を与えます
  • double 型の HeightRequestSizeRequestRequest プロパティに影響を与えます
  • double 型の MinimumWidthRequestSizeRequestMinimum プロパティに影響を与えます
  • double 型の MinimumHeightRequestSizeRequestMinimum プロパティに影響を与えます

無限の制約

GetSizeRequest (または Measure) および OnSizeRequest (または OnMeasure) に渡される制約の引数は、無限 (つまり、Double.PositiveInfinity の値) です。 ただし、これらのメソッドから返される SizeRequest には、無限ディメンションを含めることはできません。

無限の制約では、要求されたサイズには要素の自然なサイズが反映される必要があることを示します。 垂直の StackLayout では、無制限の高さ制約を使用して、その子に対して GetSizeRequest (または Measure) を呼び出します。 水平のスタック レイアウトでは、無制限の幅制約を使用して、その子に対して GetSizeRequest (または Measure) を呼び出します。 AbsoluteLayout では、無制限の幅および高さ制約を使用して、その子に対して GetSizeRequest (または Measure) を呼び出します。

プロセス内のピーク

ExploreChildSize では、シンプルなレイアウトに対する制約とサイズ要求の情報を表示します。

Layout<View> からの派生

カスタム レイアウト クラスは、Layout<View> から派生します。 次の 2 つの役割があります。

  • OnMeasure をオーバーライドして、レイアウトのすべての子に対して Measure を呼び出します。 レイアウト自体に要求されたサイズを返します
  • LayoutChildren をオーバーライドして、レイアウトのすべての子に対して Layout を呼び出します。

これらのオーバーライドでの for または foreach ループでは、IsVisible プロパティが false に設定されているすべての子をスキップする必要があります。

OnMeasure の呼び出しは保証されません。 レイアウトの親によってレイアウトのサイズが管理されている場合 (たとえば、1 ページを埋めるレイアウトなど)、OnMeasure は呼び出されません。 このため、LayoutChildren では、OnMeasure の呼び出し時に取得された子のサイズに依存することはできません。 多くの場合、LayoutChildren 自体によって、レイアウトの子に対して Measure を呼び出す必要があります。または、(後述するように) ご自身で何らかのサイズ キャッシュのロジックを実装することもできます。

簡単な例

VerticalStackDemo サンプルには、簡略化された VerticalStack クラスとその使用方法のデモンストレーションが含まれています。

簡略化された垂直および水平の位置決定

VerticalStack によって実行される必要があるジョブの 1 つは、LayoutChildren のオーバーライド中に行われる必要があります。 メソッドでは、子の HorizontalOptions プロパティを使用して、VerticalStack のスロット内での子の配置方法を決定します。 代わりに、静的メソッド Layout.LayoutChildIntoBoundingRect を呼び出すこともできます。 このメソッドでは、子に対して Measure を呼び出し、その HorizontalOptionsVerticalOptions プロパティを使用して、指定された四角形の中にその子を配置します。

無効化

多くの場合、要素のプロパティを変更すると、レイアウト上でのその要素の表示方法に影響します。 新しいレイアウトをトリガーするには、レイアウトを無効にする必要があります。

VisualElement では、保護されたメソッド InvalidateMeasure を定義しています。このメソッドは通常、変更されると要素のサイズに影響を与えるバインド可能なプロパティのプロパティ変更ハンドラーによって呼び出されます。 InvalidateMeasure メソッドでは、MeasureInvalidated イベントを発生させます。

Layout クラスでは、同様の InvalidateLayout という保護されたメソッドを定義しています。このメソッドは、子の位置とサイズの決定方法に影響を及ぼす変更があった場合には、Layout の派生によって必ず呼び出されます。

レイアウトをコーディングするためのいくつかのルール

  1. Layout<T> の派生によって定義されるプロパティは、バインド可能なプロパティによってサポートされている必要があります。また、そのプロパティ変更ハンドラーでは InvalidateLayout を呼び出す必要があります。

  2. アタッチされたバインド可能なプロパティを定義する Layout<T> の派生では、OnAdded をオーバーライドしてプロパティ変更ハンドラーをその子と OnRemoved に追加して、そのハンドラーを削除する必要があります。 ハンドラーでは、これらのアタッチされたバインド可能なプロパティでの変更を確認し、InvalidateLayout を呼び出して応答します。

  3. 子サイズのキャッシュを実装する Layout<T> の派生では、InvalidateLayout および OnChildMeasureInvalidated をオーバーライドして、これらのメソッドが呼び出されたときにキャッシュをクリアします。

プロパティが設定されたレイアウト

Xamarin.FormsBook.Toolkit にある WrapLayout クラスでは、すべての子が同じサイズであることを前提とし、1 つの行 (または列) から次へと子を折り返します。 StackLayout のような Orientation プロパティと、Grid のような ColumnSpacing および RowSpacing プロパティを定義し、子のサイズをキャッシュします。

PhotoWrap サンプルでは、ストックされた写真を表示するために、ScrollView 内に WrapLayout を配置します。

制約のないディメンションは許可されない

Xamarin.FormsBook.Toolkit ライブラリにある UniformGridLayout は、その中にあるすべての子を表示することを目的としています。 したがって、制約のないディメンションを処理することはできず、その状況に遭遇した場合には、例外が発生します。

PhotoGrid サンプルでは、UniformGridLayout のデモを示しています。

Photo Grid のトリプル スクリーンショット

子の重複

Layout<T> の派生では、子が重複する場合があります。 しかし、子は、Layout メソッドが呼び出される順序ではなく、Children コレクション内の順序でレンダリングされます。

Layout クラスでは、コレクション内の子を移動できる 2 つのメソッドを定義しています。

  • 子をコレクションの先頭に移動するための LowerChild
  • 子をコレクションの末尾に移動するための RaiseChild

子が重複している場合、コレクションの末尾にある子は、コレクションの先頭にある子より優先して、視覚的に表示されます。

Xamarin.FormsBook.Toolkit ライブラリにある OverlapLayout クラスでは、アタッチされたプロパティを定義してレンダリング順序を指示し、これによって子のうちの 1 つをそれ以外よりも優先して表示できるようにします。 StudentCardFile サンプルでは、以下のデモを示しています。

Student Card File Grid のトリプル スクリーンショット

その他のアタッチされたバインド可能なプロパティ

Xamarin.FormsBook.Toolkit ライブラリにある CartesianLayout クラスでは、アタッチされたバインド可能なプロパティを定義して 2 つの Point 値と太さの値を指定し、BoxView 要素を操作して行のようにします。

UnitCube サンプルでは、それを使用して 3 次元のキューブを描画しています。

Layout と LayoutTo

Layout<T> の派生では、Layout ではなく LayoutTo を呼び出して、レイアウトをアニメーション化することができます。 これは AnimatedCartesianLayout クラスによって行われ、AnimatedUnitCube サンプルではそのデモを示しています。