[.NET MAUI] 入力用リスト
レンタカー予約画面で「車種」の入力欄をタップすると、画面下からニョキッと入力用のリストを表示したい。イメージとしてはこれに近い。
AbsoluteLayoutによるUIの重ね
ページ(最上位のGrid)の前面に入力用リスト(ListView)を重ねて表示させるため、ページ全体をAbsoluteLayoutでレイアウトし直す。
<ContentPage ...
...
+ <AbsoluteLayout>
+ <Grid RowSpacing="0" ColumnSpacing="0"
+ AbsoluteLayout.LayoutFlags="PositionProportional,HeightProportional"
+ AbsoluteLayout.LayoutBounds="0.5,0,375,1">
- <Grid RowSpacing="0" ColumnSpacing="0">
...
</Grid>
+ <!-- ▼車種入力用リスト -->
+ <ListView x:Name="CarTypeList"
+ AbsoluteLayout.LayoutFlags="PositionProportional"
+ AbsoluteLayout.LayoutBounds="0.5,1,375,300">
+ </ListView>
+
+ </AbsoluteLayout>
</ContentPage>
AbsoluteLayout.LayoutFlagsとAbsoluteLayout.LayoutBoundsの使い方はこれが参考になる。
今回の場合、一番親のGridはLayoutFlagsにPositionProportionalとHeightProportionalを指定しているため、LayoutBoundsのカンマ区切りの4つの値は下記のように解釈される。
※PositionPropotionalは「XPropotional,YPropotional」と等価。
値 | |||
---|---|---|---|
0.5 | X方向の位置 | 相対指定 | 中寄せ |
0 | Y方向の位置 | 相対指定 | 上寄せ |
375 | 幅 | 絶対指定 | 375 |
1 | 高さ | 相対指定 | 縦方向いっぱい |
リストの内容生成
車種入力用リストの内容を空っぽではなく「コンパクト」「ミニバン」とかにしてみる。
リストはテンプレートを使って生成する。「.NET MAUIによるマルチプラットフォームアプリ開発」という書籍を参照してコードを書いたが、なぜか書籍通り<ViewCell>に<Label>を入れる方法ではラベルの文字列が表示されなかったので、下記の通り<TextCell>を使用する。
<!-- ▼車種入力用リスト -->
<ListView x:Name="CarTypeList"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,1,375,300">
+ <ListView.ItemTemplate>
+ <DataTemplate>
+ <TextCell Text="{Binding Name}" />
+ </DataTemplate>
+ </ListView.ItemTemplate>
</ListView>
コードビハインドは下記の通り。ページロード時にリストを生成する。CarTypeという独自のクラスを定義しておき、CarTypeクラスのNameプロパティをListViewのTextCellの文字列にバインドする。
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
+ this.Loaded += MainPage_Loaded;
}
+ private void MainPage_Loaded(object sender, EventArgs e)
+ {
+ var lst = new List<CarType>();
+ lst.Add(new CarType() { Name = "コンパクト" });
+ lst.Add(new CarType() { Name = "ミニバン" });
+ lst.Add(new CarType() { Name = "セダン" });
+ lst.Add(new CarType() { Name = "ワゴン" });
+ lst.Add(new CarType() { Name = "SUV" });
+ lst.Add(new CarType() { Name = "スポーツ" });
+ lst.Add(new CarType() { Name = "商用" });
+ CarTypeList.ItemsSource = lst;
+ }
}
+public class CarType
+{
+ public string Name { get; set; } = "";
+}
めでたく車種入力用リストが表示された!
リストの表示/非表示
このままではページを表示した直後に車種入力用リストが表示されたままになってしまうので、下記のような動作にする。
- ページ表示直後はリスト非表示
- 車種の入力欄にフォーカスしたらリスト表示
- フォーカスがロストしたらリスト非表示
<controls:BHPANEL LabelText="車種" />
<Entry x:Name="CarTypeEntry"
WidthRequest="180"
+ Focused="CarType_Focused"
+ Unfocused="CarType_Unfocused"
private void MainPage_Loaded(object sender, EventArgs e)
{
...
+ // 車種入力用リストを非表示にする
+ CarType_Unfocused(sender, e);
}
+ private void CarType_Focused(object sender, EventArgs e)
+ {
+ CarTypeList.IsVisible = true;
+ }
+
+ private void CarType_Unfocused(object sender, EventArgs e)
+ {
+ CarTypeList.IsVisible = false;
+ }
ページ表示直後のリスト非表示について、本来はMainPage.xaml側で<ListView ... IsVisible="false">としたかったが、AbsoluteLayoutとの兼ね合いか、なぜかその後IsVisibleをtrueに変えても何も描画されないバギーな動きをしたので、やむを得ずMainPage_Loaded内でCarType_Unfocusedを呼ぶことにした。
アニメーション
こちらの記事を参考に、リスト表示/非表示にちょっとしたアニメーションを付けてみる。
アニメーションはViewExtensions.TranslateToを使い、リストの高さ分だけイージングかけて上下移動させる。
+ private async void CarType_Focused(object sender, EventArgs e)
- private void CarType_Focused(object sender, EventArgs e)
{
+ await CarTypeList.TranslateTo(0, CarTypeList.Height, 0);
CarTypeList.IsVisible = true;
+ await CarTypeList.TranslateTo(0, 0, 200, Easing.CubicOut);
}
+ private async void CarType_Unfocused(object sender, EventArgs e)
- private void CarType_Unfocused(object sender, EventArgs e)
{
+ await CarTypeList.TranslateTo(0, CarTypeList.Height, 200, Easing.CubicOut);
CarTypeList.IsVisible = false;
}
リスト選択時の動作
リストから選択した車種を入力欄に反映する。
<!-- ▼車種入力用リスト -->
<ListView x:Name="CarTypeList"
+ ItemSelected="CarTypeList_ItemSelected"
+ void CarTypeList_ItemSelected(System.Object sender, Microsoft.Maui.Controls.SelectedItemChangedEventArgs e)
+ {
+ CarType item = e.SelectedItem as CarType;
+ CarTypeEntry.Text = item.Name;
+ CarTypeEntry.Unfocus();
+ }
ここまで、リストから入力する対象のUIをEntry
で作成して、Focused
とUnfocused
イベントでリストの表示/非表示を実装してきたが、iPhone実機で動作確認したところ、Entry
に対するFocused
イベントでiOS標準のソフトキーボードが表示されてしまうことが判明した。
こちらにあるEntry
クラスのHideSoftInputAsync
を使うとソフトキーボードを非表示にすることは可能だが、非表示にした瞬間Entry
に対するフォーカスも失われてしまうため、求めている動作とならなかった。
他にも、CommunityTookit.MauiのKeyboardExtensionsを使ってEntry.HideKeyboardAsync
も試してみたが、これも結果は全く同じ。
かなり試行錯誤した結果、Entry
を辞めてLabel
を継承した独自のBHEDIT
コントロールを作成することにした。入力用リストを表示するのはTapGestureRecognizer
のTapped
イベントで実装できるが、問題はフォーカスが外れた時にリストの非表示をどう実装すべきかという点。
これは、ContentPage
のかなり上の階層にあるGrid
に対してTapGestureRecognizer
を追加して、Tapped
時に入力用リストを非表示にする処理を追加することで解決。ただし、なぜかAndroidではGrid
に対するTapGestureRecognizer
が反応してくれないという別の問題が発生。ひとまず今回はiOSとMacをターゲットにしているのでこのまま先へ進むことにした。
この続きはこちら