Posted at

Xamarin.Formsで汎用的なローディング画面を作ってみる

More than 3 years have passed since last update.

Xamarin.Formsやっていてちょっと(-_-;)ってなってしまう所にIsBusyプロパティの微妙な感じがあるかと思います。

他の操作できちゃうし。

こういうのがやりたかったのです。

alt

DisplayAlertみたいな感じで使えるクルクル回るローディング画面を簡単に作れないかなぁと思って作ってみました。

まずPCLプロジェクトのみで実装しようとしてみました。

画面をマスクして背景を透過させるように実装する為には、今表示しているページのContentを使用して別ページを表示してローディングのContentと元々のContentを重ねるという手法で実装する事が出来たのですが・・・・・・遅い。

XAMLを描画し直し、バインディングをやり直しとなるので大変動作が遅くなってしまいました。

なのでプラットフォーム毎に実装する事にしました。

簡単に作りたかったのでDependencyServiceにて実装しました。

とりあえずPCL側はこんな感じ。

    public interface ILoadingMessage

{
/// <summary>ローディングを開始する</summary>
/// <param name="message"></param>
void Show(string message);

/// <summary>ローディングを終了する</summary>
void Hide();

/// <summary>状態</summary>
bool IsShow { get; }
}

AndroidはProgressDialogを表示するだけ

    public class LoadingMessage : ILoadingMessage

{
private ProgressDialog progress;
/// <summary>ローディングを開始する</summary>
/// <param name="message"></param>
public void Show(string message)
{
progress = new ProgressDialog(Forms.Context);
progress.Indeterminate = true;
progress.SetProgressStyle(ProgressDialogStyle.Spinner);
progress.SetCancelable(false);
progress.SetMessage(message);
progress.Show();
ishow = true;
}

/// <summary>ローディングを終了する</summary>
public void Hide()
{
progress?.Dismiss();
ishow = false;
}

/// <summary>状態</summary>
public bool IsShow => ishow;

private bool ishow = false;
}

iOSはここを参考にしました。

https://developer.xamarin.com/recipes/ios/standard_controls/popovers/display_a_loading_message/

    public class LoadingMessage : ILoadingMessage

{
private LoadingOverlay loadpop;
/// <summary>ローディングを開始する</summary>
/// <param name="message"></param>
public void Show(string message)
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}

var bounds = UIScreen.MainScreen.Bounds;
loadpop = new LoadingOverlay(bounds, message);
vc.Add(loadpop);
ishow = true;
}

/// <summary>ローディングを終了する</summary>
public void Hide()
{
loadpop.Hide();
ishow = false;
}

/// <summary>状態</summary>
public bool IsShow => ishow;

private bool ishow = false;
}

public class LoadingOverlay : UIView
{
// control declarations
UIActivityIndicatorView activitySpinner;
UILabel loadingLabel;

public LoadingOverlay(CGRect frame, string message) : base(frame)
{
// configurable bits
BackgroundColor = UIColor.Black;
Alpha = 0.75f;
AutoresizingMask = UIViewAutoresizing.All;

nfloat labelHeight = 22;
nfloat labelWidth = Frame.Width - 20;

// derive the center x and y
nfloat centerX = Frame.Width / 2;
nfloat centerY = Frame.Height / 2;

// create the activity spinner, center it horizontall and put it 5 points above center x
activitySpinner = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.WhiteLarge);
activitySpinner.Frame = new CGRect(
centerX - (activitySpinner.Frame.Width / 2),
centerY - activitySpinner.Frame.Height - 20,
activitySpinner.Frame.Width,
activitySpinner.Frame.Height);
activitySpinner.AutoresizingMask = UIViewAutoresizing.All;
AddSubview(activitySpinner);
activitySpinner.StartAnimating();

// create and configure the "Loading Data" label
loadingLabel = new UILabel(new CGRect(
centerX - (labelWidth / 2),
centerY + 20,
labelWidth,
labelHeight
));
loadingLabel.BackgroundColor = UIColor.Clear;
loadingLabel.TextColor = UIColor.White;
loadingLabel.Text = message;
loadingLabel.TextAlignment = UITextAlignment.Center;
loadingLabel.AutoresizingMask = UIViewAutoresizing.All;
AddSubview(loadingLabel);

}

/// <summary>
/// Fades out the control and then removes it from the super view
/// </summary>
public void Hide()
{
UIView.Animate(
0.5, // duration
() => { Alpha = 0; },
() => { RemoveFromSuperview(); }
);
}
}

結構快適に動作して良いのですが、残念ながらこれだけではボタン二度押し対策にはならないので

そこら辺は、簡単に実装できる良い案がないか考え中です。

あと、動作スレッドに関して考慮せずに作っているので呼び出し元がUIThread出ない場合には注意が必要です。