様々なところで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を使用する場合は高さのバインドは不要です。
以上です。
コメントを残す