最終更新:2019年3月8日
モバイルアプリでアップロードなどの処理をする時、ユーザーが操作できないように画面をロックしたいことってありますよね。今回はXamarin.Formsを用いてどのように実現するかを調べたのでまとめてみようと思います。
環境
Visual Studio Community 2017 for Mac
Xamarin.Forms (3.6.0.220655)
目次
方法1:AbsoluteLayoutでContentViewとFrameを使う
共通コードの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:local="clr-namespace:XF_Overlay" x:Class="XF_Overlay.MainPage"> <AbsoluteLayout> <StackLayout AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" VerticalOptions="Center"> <Button Clicked="OnOverlay" Text="Overlay"/> </StackLayout> <ContentView x:Name="bglayer" BackgroundColor="Black" Opacity="0.4" IsVisible="false" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" /> <Frame x:Name="frame" IsVisible="false" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.5,0.5,AutoSize,AutoSize"> <StackLayout> <ActivityIndicator Color="Black" IsRunning="true"/> <Label Text="処理中です" /> </StackLayout> </Frame> </AbsoluteLayout> </ContentPage>
コードビハインドは以下の通りです。
using System; using System.Threading.Tasks; using Rg.Plugins.Popup.Services; using Xamarin.Forms; namespace XF_Overlay { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } async void OnOverlay(object sender, EventArgs e) { bglayer.IsVisible = true; frame.IsVisible = true; await Task.Delay(2000); bglayer.IsVisible = false; frame.IsVisible = false; } } }
Overlayボタンを押すと以下の画像のように画面全体をFrameが覆うことによって操作をロックすることができます。
しかし、ナビゲーションページを使う場合はナビゲーションバーまでは覆うことはできません。例えば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:local="clr-namespace:XF_Overlay" x:Class="XF_Overlay.MainPage"> <!--ここから追加--> <ContentPage.ToolbarItems> <ToolbarItem Text="Test" /> </ContentPage.ToolbarItems> <!--追加終わり--> <AbsoluteLayout> <StackLayout AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" VerticalOptions="Center"> <Button Clicked="OnOverlay" Text="Overlay"/> </StackLayout> <ContentView x:Name="bglayer" BackgroundColor="Black" Opacity="0.4" IsVisible="false" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" /> <Frame x:Name="frame" IsVisible="false" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.5,0.5,AutoSize,AutoSize"> <StackLayout> <ActivityIndicator Color="Black" IsRunning="true"/> <Label Text="処理中です" /> </StackLayout> </Frame> </AbsoluteLayout> </ContentPage>
下の画像のようにナビゲーションバーが隠れないのでTest
ボタンを押すことができてしまいます。
方法2:Rg.Plugins.Popupを使う
この問題を解決するため、Rg.Plugins.Popupというライブラリを使用します。
GitHub - rotorgames/Rg.Plugins.Popup: Xamarin Forms popup plugin
まずはNugetで共通コード、iOS、AndroidそれぞれのプロジェクトにRg.Plugins.Popupをインストールします。(画像はVisual Studio for Macです)
次にiOS、Androidそれぞれのプロジェクトに追記をします。
iOSのAppDelegate.cs
のFinishedLaunching
に以下のように追記が必要です。
public override bool FinishedLaunching(UIApplication app, NSDictionary options) { Rg.Plugins.Popup.Popup.Init(); //ここを追加 global::Xamarin.Forms.Forms.Init(); LoadApplication(new App()); return base.FinishedLaunching(app, options); }
AndroidのMainActivity.cs
のOnCreate
には以下を追記します。
protected override void OnCreate(Bundle savedInstanceState) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(savedInstanceState); Rg.Plugins.Popup.Popup.Init(this, savedInstanceState); //ここを追加 global::Xamarin.Forms.Forms.Init(this, savedInstanceState); LoadApplication(new App()); }
次に画面をロックするためのポップアップ画面を新規作成し、以下のようにします。
<?xml version="1.0" encoding="UTF-8"?> <pages:PopupPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XF_Overlay.LoadingPopupPage" CloseWhenBackgroundIsClicked="False"> <AbsoluteLayout> <Frame AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.5,0.5,AutoSize,AutoSize"> <StackLayout> <ActivityIndicator Color="Black" IsRunning="True" IsEnabled="True" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="70" WidthRequest="70"/> <Label Text="処理中です" /> </StackLayout> </Frame> </AbsoluteLayout> </pages:PopupPage>
最後に上記のポップアップ画面を呼び出す処理をコードビハインドに書きます。
(XAMLのButtonにClicked="Rg"
としている場合です)
async void Rg(object sender, EventArgs e) { var page = new LoadingPopupPage(); await PopupNavigation.Instance.PushAsync(page); await Task.Delay(2000); await PopupNavigation.Instance.PopAsync(); }
これでボタンを押すと画面は以下のようになります。
(iOSの画面のみですが、Androidも同じような感じになります)
ちゃんとナビゲーションバーまで画面がロックされていますね!
あとは実行中に操作ロックしたい処理をawait Task.Delay(2000)
の所に記述すれば良いと思います。
サンプルコード
https://github.com/Takahiro901/XF_Overlay
2019/3/7 追記
よく調べたらToolbarItemはIsEnabledプロパティを持っていることに気づきました。処理をしている間にこれをfalseにすれば、ナビゲーションバーを隠さなくても(つまりContentViewとFrameを使うだけで)よさそうです。
ただ、ToolbarItemのIsEnabledプロパティはなぜかコードビハインドからは操作できないようなので、ViewModelを作ってバインディングすることで処理中はボタンを押せないようにすることができます。コードは下のような感じです。
2019/3/8 追記
すみません、これを記述した翌日に試したらこの方法ではうまく動かなくなってしまいました。代わりにToolbarItemのCommandプロパティをバインドすることで実現することができました。詳細は以下の記事をご覧ください。
【Xamarin.Forms】ToolbarItemを有効化・無効化する方法 - takataka430’s blog
参考にしたサイト
GitHub - rotorgames/Rg.Plugins.Popup: Xamarin Forms popup plugin
Xamarin.Forms で ListView を最後まで表示するとクルクルを表示してその間に処理をするには~その2~ - Xamarin 日本語情報