かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Xamarin.Formsで画面レイアウトを作るためのレイアウトコントロールの必要最低限

Xamarin.Formsで思い通りのレイアウトを組みたい。 そんな時には、レイアウト系のコントロールを押さえておくといいです。

公式ドキュメント

Layouts - Xamarin

Layoutコントロール

Xamarin.Formsでは、複数のコントロールを配置するときには、あらかじめ定義されている子要素をレイアウトする機能をもったコントロールに対して、コントロールを配置、もしくはレイアウトコントロールをネストさせて配置します。以下のコントロールが提供されています。

  • StackLayout
  • AbsoluteLayout
  • RelativeLayout
  • Grid
  • ScrillViewer

ここでは、おさえておくべき必要最低限のレイアウトコントロールとしてStackLayoutGridについて紹介したいと思います。

StackLayout

StackLayoutは、名前の通り子要素を縦方向、横方向に並べて配置するコントロールになります。デフォルトでは縦方向に子要素を並べて配置します。デフォルトの挙動を確認するためのXAMLを以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <BoxView Color="Red" />
    <BoxView Color="Blue" />
    <BoxView Color="Green" />
    <BoxView Color="Aqua" />
  </StackLayout>
</ContentPage>

実行すると以下のような表示になります。

f:id:okazuki:20161208222547p:plain

縦と横の表示を切り替えるにはOrientationプロパティを指定します。VerticalがデフォルトでHorizontalを指定することで、横方向に並ぶようになります。XAMLを以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout Orientation="Horizontal">
    <BoxView Color="Red" />
    <BoxView Color="Blue" />
    <BoxView Color="Green" />
    <BoxView Color="Aqua" />
  </StackLayout>
</ContentPage>

実行すると以下のような表示になります。

f:id:okazuki:20161208222844p:plain

Spacing

Spacingプロパティを使うことでコントロールを並べるときの余白を指定することもできます。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout Orientation="Horizontal"
               Spacing="20">
    <BoxView Color="Red" />
    <BoxView Color="Blue" />
    <BoxView Color="Green" />
    <BoxView Color="Aqua" />
  </StackLayout>
</ContentPage>

実行すると以下のようになります。

f:id:okazuki:20161208223029p:plain

コントロール間のスペースが広くなっていることが確認できます。

LayoutOptions

さらにコントロールのHorizontalOptionsプロパティとVerticalOptionsを使うことで子コントロールがStackLayout上で占める割合をある程度自由に設定することができます。

HorizontalOptionsプロパティとVerticalOptionsプロパティには以下の値が設定できます。

  • Start: 開始位置にレイアウトする
  • Center: 中央にレイアウトする
  • End: 終端にレイアウトする
  • Fill: いっぱいに広げる
  • StartAndExpand: 開始位置にレイアウトして余白を埋めつくす
  • CenterAndExpand: 中央にレイアウトして余白を埋め尽くす
  • EndAndExpand: 終了位置にレイアウトして余白を埋め尽くす
  • FillAndExpand: いっぱいに広げて余白を埋め尽くす

まず、Start, Center, End, Fillを見ていきます。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout Orientation="Horizontal">
    <BoxView Color="Red" VerticalOptions="Start" />
    <BoxView Color="Blue" VerticalOptions="End" />
    <BoxView Color="Green" VerticalOptions="Center" />
    <BoxView Color="Aqua" VerticalOptions="Fill" /> <!-- Default -->
  </StackLayout>
</ContentPage>

実行すると以下のようになります。

f:id:okazuki:20161208223900p:plain

次にAndExpandがついているオプションについての挙動を見てみます。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <BoxView Color="Red" VerticalOptions="StartAndExpand" />
    <BoxView Color="Blue" />
  </StackLayout>
</ContentPage>

実行すると、赤色が上端に配置されて余白を埋め尽くします。そして、青色が赤色が余白を埋め尽くすため下端に表示されます。実行結果を以下に示します。

f:id:okazuki:20161208224159p:plain

次に、EndAndExpandの挙動を確認します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <BoxView Color="Red" VerticalOptions="EndAndExpand" />
    <BoxView Color="Blue" />
  </StackLayout>
</ContentPage>

実行すると、赤色が余白を埋め尽くして下端に表示されます。その下に青色が表示されます。実行結果を以下に示します。

f:id:okazuki:20161208224411p:plain

次に、FillAndExpandの挙動を確認します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <BoxView Color="Red" VerticalOptions="FillAndExpand" />
    <BoxView Color="Blue" />
  </StackLayout>
</ContentPage>

実行すると、赤色が余白を埋め尽くす形でいっぱいに表示されて、残りの部分に青色が表示されます。実行結果を以下に示します。

f:id:okazuki:20161208224614p:plain

AndExpandのつくプロパティを複数使用した場合、余白は均等に割り振られます。以下のように2つのコントロールでFillAndExpandStartAndExpandを使用した場合は、上半分に赤色がいっぱいに表示され、下半分の開始位置に青色が配置されます。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <BoxView Color="Red" VerticalOptions="FillAndExpand" />
    <BoxView Color="Blue" VerticalOptions="StartAndExpand"/>
  </StackLayout>
</ContentPage>

実行結果を以下に示します。

f:id:okazuki:20161208224850p:plain

複雑なレイアウト

StackLayoutLayoutOptionsを組み合わせることで複雑なレイアウトが実現可能です。以下に例を示します。イメージとしてはTwitterクライアントのような見た目をイメージしています。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout>
    <!-- つぶやき1 -->
    <StackLayout Orientation="Horizontal"
                 VerticalOptions="Start">
      <BoxView Color="Red" />
      <StackLayout HorizontalOptions="FillAndExpand">
        <StackLayout Orientation="Horizontal">
          <StackLayout Orientation="Vertical"
                       HorizontalOptions="FillAndExpand">
            <StackLayout Orientation="Horizontal">
              <Label Text="@okazuki" />
              <Label Text="かずき@MBPぽちった"
                     HorizontalOptions="FillAndExpand" />
            </StackLayout>
            <Label Text="XXXXXXXXXXXXXXXXXXXX" />
          </StackLayout>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="EndAndExpand">
          <Button Text="Like" 
                  HorizontalOptions="End" />
          <Button Text="RT" 
                  HorizontalOptions="End" />
          <Button Text="Quote" 
                  HorizontalOptions="End" />
        </StackLayout>
      </StackLayout>
    </StackLayout>

    <!-- つぶやき2 -->
    <StackLayout Orientation="Horizontal"
                 VerticalOptions="Start">
      <BoxView Color="Red" />
      <StackLayout HorizontalOptions="FillAndExpand">
        <StackLayout Orientation="Horizontal">
          <StackLayout Orientation="Vertical"
                       HorizontalOptions="FillAndExpand">
            <StackLayout Orientation="Horizontal">
              <Label Text="@okazuki" />
              <Label Text="かずき@MBPぽちった"
                     HorizontalOptions="FillAndExpand" />
            </StackLayout>
            <Label Text="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" />
          </StackLayout>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="EndAndExpand">
          <Button Text="Like" 
                  HorizontalOptions="End" />
          <Button Text="RT" 
                  HorizontalOptions="End" />
          <Button Text="Quote" 
                  HorizontalOptions="End" />
        </StackLayout>
      </StackLayout>
    </StackLayout>
  </StackLayout>
</ContentPage>

実行結果を以下に示します。

f:id:okazuki:20161208230620p:plain

このように、StackLayoutLayoutOptionsを組み合わせてネストさせることで、かなりのレイアウトを組むことができます。

Grid

次に、Gridについて説明します。Gridは、名前の通り格子状にレイアウトの中を区切って、そこにコントロールを配置していきます。行の定義はGridRowDefinitionsプロパティに対してRowDefinitionを追加することで定義できます。列の定義はColumnDefinitionsプロパティに対してColumnDefinitionを追加することで定義できます。デフォルトでは均等に幅と高さが割り振られます。

コントロールの配置はGrid.Row添付プロパティで行(0オリジン)を指定してGrid.Columnプロパティで列(0オリジン)を指定します。

3x3の格子状に画面を区切った場合のコード例を以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <Grid>
    <!-- 行の定義 -->
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>    
    <!-- 列の定義 -->
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    
    <BoxView Color="Red" /> <!-- デフォルトは0,0に配置される -->
    <BoxView Color="Blue" 
             Grid.Row="0"
             Grid.Column="1" />
    <BoxView Color="Aqua" 
             Grid.Row="0"
             Grid.Column="2" />
    <BoxView Color="Maroon" 
             Grid.Row="1"
             Grid.Column="0" />
    <BoxView Color="Navy" 
             Grid.Row="1"
             Grid.Column="1" />
    <BoxView Color="Silver" 
             Grid.Row="1"
             Grid.Column="2" />
    <BoxView Color="Purple" 
             Grid.Row="2"
             Grid.Column="0" />
    <BoxView Color="Lime" 
             Grid.Row="2"
             Grid.Column="1" />
    <BoxView Color="Yellow" 
             Grid.Row="2"
             Grid.Column="2" />
             
  </Grid>
</ContentPage>

実行結果を以下に示します。

f:id:okazuki:20161208231350p:plain

幅と高さの指定

RowDefinitionにはHeightプロパティで行の高さを指定できます。ColumnDefinitionにはWidthプロパティで列の幅を指定できます。指定方法には、数字を指定して固定幅を指定する方法と、1*, 2*のように*を使用して余白を占める割合を比率で指定する方法とAutoを指定して中身のサイズに合わせる方法があります。

コード例を以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <Grid>
    <!-- 行の定義 -->
    <Grid.RowDefinitions>
      <RowDefinition Height="15" /> <!-- 固定 -->
      <RowDefinition Height="1*" /> <!-- 余白を1対2に分割 -->
      <RowDefinition Height="2*" />
    </Grid.RowDefinitions>    
    <!-- 列の定義 -->
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" /> <!-- 中身の幅に応じて設定 -->
      <ColumnDefinition Width="*" /> <!-- デフォルト値は*(1*と同じ意味) -->
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    
    <BoxView Color="Red" /> <!-- デフォルトは0,0に配置される -->
    <BoxView Color="Blue" 
             Grid.Row="0"
             Grid.Column="1" />
    <BoxView Color="Aqua" 
             Grid.Row="0"
             Grid.Column="2" />
    <BoxView Color="Maroon" 
             Grid.Row="1"
             Grid.Column="0" />
    <BoxView Color="Navy" 
             Grid.Row="1"
             Grid.Column="1" />
    <BoxView Color="Silver" 
             Grid.Row="1"
             Grid.Column="2" />
    <BoxView Color="Purple" 
             Grid.Row="2"
             Grid.Column="0" />
    <BoxView Color="Lime" 
             Grid.Row="2"
             Grid.Column="1" />
    <BoxView Color="Yellow" 
             Grid.Row="2"
             Grid.Column="2" />
             
  </Grid>
</ContentPage>

実行結果を以下に示します。

f:id:okazuki:20161208231859p:plain

複数行・複数列の占有

今までは格子状に区切った中の1つのセルに1つのコントロールを置いてきました。Gridコントロールでは、これに加えて複数のセルにまたがる形でコントロールを配置することができます。Grid.RowSpan添付プロパティと、Grid.ColumnSpan添付プロパティを使用することで行の占有数と列の占有数を指定できます。

コード例を以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <Grid>
    <!-- 行の定義 -->
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>    
    <!-- 列の定義 -->
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    
    <BoxView Color="Red" 
             Grid.RowSpan="2"
             Grid.ColumnSpan="3"/>
    <BoxView Color="Yellow" 
             Grid.Row="2"
             Grid.Column="1"
             Grid.ColumnSpan="2" />
             
  </Grid>
</ContentPage>

実行すると以下のようになります。

f:id:okazuki:20161208232709p:plain

複雑なレイアウト

Gridを使うことで複雑なレイアウトを組むことが簡単にできます。StackLayoutで示した表示と同じような表示をGridでもやってみたいと思います。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage">
  <StackLayout VerticalOptions="Start">
    <!-- つぶやき1 -->
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />        
        <RowDefinition Height="Auto" />        
        <RowDefinition Height="Auto" />        
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <BoxView Color="Red"
               Grid.RowSpan="3" />
      <StackLayout Orientation="Horizontal"
                   Grid.Column="1"
                   Grid.ColumnSpan="4">
        <Label Text="@okazuki" />
        <Label Text="かずき@MBPぽちった" />
      </StackLayout>
      <Label Text="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
             Grid.Row="1"
             Grid.Column="1"
             Grid.ColumnSpan="4" />
      <Button Text="Like"
              Grid.Row="2"
              Grid.Column="2" />
      <Button Text="RT"
              Grid.Row="2"
              Grid.Column="3" />
      <Button Text="Quote"
              Grid.Row="2"
              Grid.Column="4" />
    </Grid>
    
    <!-- つぶやき2 -->
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />        
        <RowDefinition Height="Auto" />        
        <RowDefinition Height="Auto" />        
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <BoxView Color="Red"
               Grid.RowSpan="3" />
      <StackLayout Orientation="Horizontal"
                   Grid.Column="1"
                   Grid.ColumnSpan="4">
        <Label Text="@okazuki" />
        <Label Text="かずき@MBPぽちった" />
      </StackLayout>
      <Label Text="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
             Grid.Row="1"
             Grid.Column="1"
             Grid.ColumnSpan="4" />
      <Button Text="Like"
              Grid.Row="2"
              Grid.Column="2" />
      <Button Text="RT"
              Grid.Row="2"
              Grid.Column="3" />
      <Button Text="Quote"
              Grid.Row="2"
              Grid.Column="4" />
    </Grid>
  </StackLayout>
</ContentPage>

実行結果を以下に示します。

f:id:okazuki:20161208233621p:plain

余白

最後に余白について説明します。余白の設定は、PaddingプロパティとMarginプロパティがあります。Paddingプロパティがコントロールの外側に余白を設けます。Marginプロパティがコントロールの内側に余白を設けます。プロパティの設定方法は、4方向に同一の値を設定する方法と、左右と上下にそれぞれ同じ値を設定する方法と、上下左右に個別の値を設定する方法があります。

10のように単一の値を設定すると上下左右にすべて10の余白が設定されます。10,5のように2つの値を設定すると左右に10、上下に5の余白が設定されます。5,10,15,20のように4つの値を設定すると左、上、右、下の順番で余白が設定されます。

コード例を以下に示します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp15.Views.MainPage"
             Title="MainPage"
             Padding="20">
  <Grid Margin="10,5">
    <BoxView Color="Red" />
  </Grid>
</ContentPage>

ページのPaddingに20を設定して上下左右に20の余白を持たせています。さらにGridでMarginを指定して左右に10、上下に5の余白を持たせています。

実行結果を以下に示します。

f:id:okazuki:20161208234344p:plain

まとめ

Xamarin.Formsには、いくつかのレイアウトコントロールが定義されていますが、基本的なレイアウトに関していうとStackLayoutGridを組み合わせることで大体実現可能です。まず、とっかかりとしてこの2つのレイアウトコントロールの使い方をマスターして思い通りのページを作れるようになりましょう。