様々なところでListViewは使われると思いますが、プロパティのHeaderを使用すると一緒にスクロールされてしまいます。

今回はそんなListViewに『AbsoluteLayout』を使用して『固定ヘッダー』を付けてみようと思います。

『AbsoluteLayout』の基本的な使い方は下記をご参照ください。

ちなみにStackLayoutを使うとAbsoluteLayoutよりも簡単に作成できますが、例えば後述する『リスト上の右端にイメージボタンを配置』などが出来ません。

AbsoluteLayoutの基本

リンクだけだと飛ぶのが面倒だ、という方にサンプルだけご紹介します。

とりあえずただ貼り付けます。

※分かりやすいようにLayoutごとに色付けしてあります。

    <AbsoluteLayout BackgroundColor="Aqua">
        
    </AbsoluteLayout>

VerticalOptions等の指定無しに下記のように画面いっぱいまで広がります。

そこにStackLayoutを配置してみます。

    <AbsoluteLayout BackgroundColor="Aqua">
        <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional"
                     AbsoluteLayout.LayoutBounds="1,1,50,50"
                     BackgroundColor="Red"/>
    </AbsoluteLayout>

簡単にコードを説明すると、下記のコードでAbsoluteLayout内の位置を元にコントロールを配置していく、と定義しています。

AbsoluteLayout.LayoutFlags="PositionProportional"

次にAbsoluteLayout内の位置指定と、配置するコントロールのサイズを指定しています。

AbsoluteLayoutの左上が(0,0)で右下が(1,1)という感じです。

なので下記のコードだと『右下(1,1)に50×50(50,50)サイズで配置』となります。

AbsoluteLayout.LayoutBounds="1,1,50,50"

実際の画面がこちら。

今回はStackLayoutを表示していますが、Opacity(不透明度:小さいほど透けてる)などを設定した画像を張れば、某フリマアプリとかにある右下の出品とか追加ボタンが出来ます。

画像にタップイベントを仕込むのは今回は省略します。

AbsoluteLayoutにおけるコントロールのサイズ指定

では早速、AbsoluteLayoutにListViewを配置してみましょう。

    <AbsoluteLayout BackgroundColor="Aqua">
        <ListView BackgroundColor="Red"/>
    </AbsoluteLayout>

すると下記のような画面になります。

水色がAbsoluteLayoutの領域、赤色がListViewの領域です。

何故か、AbsoluteLayoutを全て埋めてくれません。

VerticalOptionsを設定したり、Gridを使っても下端までListViewが伸びません。

(タイトルにはAbsoluteLayoutにおける、と書きましたが、Layout全般に言えることかもしれません。)

そこで下記のようにAbsoluteLayoutの高さとListViewの高さをバインドします。

    <AbsoluteLayout x:Name="Absolute"
                    BackgroundColor="Aqua">
        <ListView HeightRequest="{Binding Path=Height,Source={x:Reference Absolute}}"
                  BackgroundColor="Red"/>
    </AbsoluteLayout>

すると、AbsoluteLayoutいっぱいまでListViewの領域が広がります。

デバイスによって様々なサイズがある為、ListViewの高さは固定値をとれません。

ちょっと面倒ですが、こうしてListViewの高さをAbsoluteLayoutに合わせることが出来ました。

固定ヘッダーの作成

最初にコードを載せてしまいます。

MainPage.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"
             x:Class="BlogContents.Views.MainPage"
             Title="{Binding Title}">
    <Grid RowSpacing="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="9*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0"
              BackgroundColor="Aqua">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0"
                   Text="都道府県"/>
            <Label Grid.Column="1"
                   Text="市区町村"/>
            <Label Grid.Column="2"
                   Text="特産品その①"/>
            <Label Grid.Column="3"
                   Text="特産品その②"/>
        </Grid>
        <AbsoluteLayout Grid.Row="1"
                        x:Name="Absolute">
            <ListView HeightRequest="{Binding Path=Height,Source={x:Reference Absolute}}"
                      ItemsSource="{Binding List}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="1*"/>
                                    <ColumnDefinition Width="1*"/>
                                    <ColumnDefinition Width="1*"/>
                                    <ColumnDefinition Width="1*"/>
                                </Grid.ColumnDefinitions>
                                <Label Grid.Column="0"
                                       Text="{Binding Prefecture}"/>
                                <Label Grid.Column="1"
                                       Text="{Binding City}"/>
                                <Label Grid.Column="2"
                                       Text="{Binding MainProduct}"/>
                                <Label Grid.Column="3"
                                       Text="{Binding SubProduct}"/>
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </AbsoluteLayout>
    </Grid>
</ContentPage>

MainPageViewModel.cs

using BlogContents.Entities;
using Prism.Navigation;
using System.Collections.ObjectModel;

namespace BlogContents.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public ObservableCollection<MainPageEntity> List { get; set; } = new ObservableCollection<MainPageEntity>();

        public MainPageViewModel(INavigationService navigationService)
            : base(navigationService)
        {
            Title = "Main Page";

            List.Add(new MainPageEntity()
            {
                Prefecture = "北海道",
                City = "札幌",
                MainProduct = "味噌ラーメン",
                SubProduct = "じゃがポックル"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "青森",
                City = "津軽",
                MainProduct = "りんご",
                SubProduct = "にんにくラーメン"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "秋田",
                City = "秋田",
                MainProduct = "きりたんぽ",
                SubProduct = "鰰"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "岩手",
                City = "盛岡",
                MainProduct = "冷麺",
                SubProduct = "じゃじゃ麺"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "宮城",
                City = "仙台",
                MainProduct = "牛タン",
                SubProduct = "ずんだ"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "山形",
                City = "山形",
                MainProduct = "芋煮",
                SubProduct = "さくらんぼ"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "福島",
                City = "福島",
                MainProduct = "桃",
                SubProduct = "凍天"
            });
            List.Add(new MainPageEntity()
            {
                Prefecture = "東京",
                City = "品川",
                MainProduct = "ビル",
                SubProduct = "人ごみ"
            });
        }
    }
}

MainPageEntity.cs

namespace BlogContents.Entities
{
    public class MainPageEntity
    {
        public string Prefecture { get; set; }
        public string City { get; set; }
        public string MainProduct { get; set; }
        public string SubProduct { get; set; }
    }
}

以上のコードで生成されるのが下記の画面です。

ヘッダーを固定したうえでListViewを下端まで表示することが出来ました。

表示されてめでたしめでたしなのですが、1点だけ注意点があります。

それはAbsoluteLayoutの適用範囲に関して。

XAMLを参照していただきたいのですが、AbsoluteLayoutはListViewしか内包していません。

ここをヘッダーまで含めてしまうと、またもや下端まで届かなくなるのですが、その上で無理やりサイズ調整となると、AbsoluteLayoutの高さからヘッダーの高さを引いた高さをListViewの高さにバインドしなくてはいけなくなります。

XamarinではMultiBindingが標準で搭載されておらず、調べてみると誰かが実装してくれたコードなども見つかるのですが、依存関係などで動かなかったりしてハードルがめちゃくちゃ高いです。

※StackLayoutを使用する場合は高さのバインドは不要です。

以上です。

スポンサーリンク