takataka430’s blog

Xamarin.Formsを中心に勉強したことのまとめを書きます

Xamarin.Formsで画面の操作をロックする方法(ナビゲーションバー含む)

最終更新: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が覆うことによって操作をロックすることができます。

f:id:takataka430:20190119213003p:plain

しかし、ナビゲーションページを使う場合はナビゲーションバーまでは覆うことはできません。例えば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ボタンを押すことができてしまいます。

f:id:takataka430:20190119213446p:plain

方法2:Rg.Plugins.Popupを使う

この問題を解決するため、Rg.Plugins.Popupというライブラリを使用します。

GitHub - rotorgames/Rg.Plugins.Popup: Xamarin Forms popup plugin

まずはNugetで共通コード、iOSAndroidそれぞれのプロジェクトにRg.Plugins.Popupをインストールします。(画像はVisual Studio for Macです)

f:id:takataka430:20190119213454p:plain

次にiOSAndroidそれぞれのプロジェクトに追記をします。

iOSAppDelegate.csFinishedLaunchingに以下のように追記が必要です。

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);
}

AndroidMainActivity.csOnCreateには以下を追記します。

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も同じような感じになります)

f:id:takataka430:20190119213729p:plain

ちゃんとナビゲーションバーまで画面がロックされていますね!
あとは実行中に操作ロックしたい処理を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 日本語情報