显示带有 xamarin 表单的弹出窗口

Posted

技术标签:

【中文标题】显示带有 xamarin 表单的弹出窗口【英文标题】:Display a popup with xamarin forms 【发布时间】:2017-11-26 08:00:20 【问题描述】:

我想在 xamarin 表单应用程序 (ios/android PCL) 中显示一个小弹出窗口

我实际上正在使用 ContentPage(XAML + C# 代码)

我正在显示这个弹出窗口:

await Navigation.PushModalAsync(mypopupinstance)

它工作正常,但弹出窗口是全屏的。我只想要一个小弹出窗口,我想看看后面是什么。

谢谢

【问题讨论】:

【参考方案1】:

您需要在别处寻找这种功能。 Rotorgames 的 Popup 插件就是一个这样的库:https://github.com/rotorgames/Rg.Plugins.Popup

【讨论】:

【参考方案2】:

模态页面不能这样呈现。

对于小弹出窗口,您可以使用

DisplayAlert() 

在页面内。

如果您想要更可定制的内容,只需将您的页面内容包装在相对布局或网格中,然后将弹出窗口添加到您的正常内容之上。

【讨论】:

你能告诉我更多关于在 à displayalert 中放置页面的信息吗?我想按语法关闭弹出窗口(里面的活动指示器)【参考方案3】:

我正在处理同样的问题,到目前为止,我能够创建一个可以容纳内容页面的弹出窗口。 我很乐意分享我目前的状态。请注意,我将尽可能缩短代码示例,因此将其简化为简单的加载和文本提示对话框。

接近

在使用 Acr.UserDialogs 库一段时间后,由于我的个人需求,我觉得需要有我可以自定义的对话框。我还想尽量减少依赖插件的必要性。 理想情况下,应该通过简单的调用来调用这样的对话框,例如:

Dialogs.ShowLoading();

string result = Dialogs.ShowPrompt();

与 Xamarin.Forms 一样,很明显我们需要依赖服务实现才能使其工作。

共享代码库

我们创建一个基本界面“IDialogs.cs”:

public interface IDialogs

    bool IsDialogOpen();
    void ShowLoading(LoadingDialog dialog);

    void ShowPrompt(PromptDialog dialog);

    void HideDialog();

接下来要做的是有一个静态对话框类,它可以从需要对话框的每个页面调用。 “Dialogs.cs”:

public static class Dialogs

    private static IDialogs dialogService = DependencyService.Get<IDialogs>();

    public static bool IsDialogOpen()
    
        return dialogService.IsDialogOpen();
    

    public static void ShowLoading()
    
        LoadingDialog dlg = new LoadingDialog();
        dialogService.ShowLoading(dlg);
    

    public static Task<string> ShowPromptText()
    
        TaskCompletionSource<string> dialogCompletion = new TaskCompletionSource<string>();
        PromptDialog dialog = new PromptDialog();

        dialog.Canceled += (object sender, object result) =>  dialogService.HideDialog(); dialogCompletion.SetResult((string)result); ;
        dialog.Confirmed += (object sender, object result) =>  dialogService.HideDialog(); dialogCompletion.SetResult((string)result); ;
        dialogService.ShowPrompt(dialog);
        return dialogCompletion.Task;
    

    public static void HideDialog()
    
        dialogService.HideDialog();
    

您会注意到,我们在 ShowPromptText 方法中使用了 TaskCompletionSource 和自定义事件处理程序。这使我们能够显示对话框并等待用户按下确定或取消按钮并使用返回的结果。

到目前为止,对话框已经尽可能简单了。我使用了一些额外的代码来设置样式和主题,但为了使这个答案简短而简单,我将把它省略掉。

加载对话框:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="MyApp.Dialogs.LoadingDialog" BackgroundColor="Transparent">
<ContentPage.Content>
    <Grid BackgroundColor="#bb000000">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Frame BackgroundColor="Black" CornerRadius="15" Grid.Row="1" x:Name="ContentGrid" Margin="100,0,100,0">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <ActivityIndicator Grid.Row="0" Color="White" IsRunning="True" HorizontalOptions="Center" VerticalOptions="Center"/>
            <Label x:Name="LoadingLabel" Text="Loading ..." VerticalOptions="End"  HorizontalOptions="Center" Grid.Row="1" TextColor="White" />
        </Grid>
        </Frame>
    </Grid>
</ContentPage.Content>

(无需为此发布 xaml.cs 代码,因为与加载屏幕没有交互)

提示对话框

PromptDialog.Xaml:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:dialogs="clr-namespace:BetterUI.Dialogs"
         x:Class="MyApp.Dialogs.PromptDialog" BackgroundColor="Transparent">
<ContentPage.Content>
    <ScrollView>
    <Grid BackgroundColor="#bb000000">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Frame x:Name="ContentGrid" Grid.Row="1" CornerRadius="15" BackgroundColor="White" Margin="50,0,50,0" Padding="0">
                <Grid  Grid.Row="1" Margin="0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="50"/>
                    </Grid.RowDefinitions>
                    <Grid x:Name="HeadingGrid" Padding="10, 5, 10, 5" Margin="-15,0,-15,0" Grid.Row="0" BackgroundColor="Black">
                        <Label x:Name="HeadingLabel" Text="Enter text" TextColor="White" Margin="20,0,20,0"/>
                    </Grid>
                    <Label l x:Name="DescriptionLabel" Text="Enter your text" Grid.Row="1" Margin="15,0,15,0"/>
                    <Entry x:Name="DialogResultText" Placeholder="Text" PlaceholderColor="LightGray" TextColor="Black" Grid.Row="2" Margin="15,0,15,0"/>
                    <Grid Grid.Row="3" ColumnSpacing="0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Button x:Name="CancelButton" Text="Cancel" Clicked="OnCancelClick" Grid.Column="0" CornerRadius="0"/>
                        <Button x:Name="ConfirmButton" Text="Okay" Clicked="OnOkayClick" Grid.Column="1" CornerRadius="0"/>
                    </Grid>
                </Grid>
            </Frame>
        </Grid>
    </ScrollView>
</ContentPage.Content>

PromptDialog.xaml.cs:

public partial class PromptDialog : ContentPage

    public event EventHandler<object> Confirmed;
    public event EventHandler<object> Canceled;

    public PromptDialog()
    
        InitializeComponent();
    
    private void OnCancelClick(object sender, EventArgs e)
    
        Canceled?.Invoke(this, string.Empty);
    

    private void OnOkayClick(object sender, EventArgs e)
    
        Confirmed?.Invoke(this, DialogResultText.Text);
    

Android 实现

首先,我们将创建之前在共享代码中创建的 IDialogs 接口的 android 实现:

[assembly: Dependency(typeof(DialogService))]
namespace MyApp.Droid.Services

/// <summary>
/// Handles displaying dialog items on screen
/// </summary>
public class DialogService : IDialogs

    private static DialogFragment currentDialog;

    /// <summary>
    /// returns if a dialog is already open
    /// </summary>
    /// <returns></returns>
    public bool IsDialogOpen()
    
        return (currentDialog != null && currentDialog.IsVisible);
    

    /// <summary>
    /// Initialize Dialog Service with activity
    /// </summary>
    /// <param name="activity">activity</param>
    public static void Init(Activity activity)
    
        Activity = activity;
    

    public static Activity Activity  get; set; 

    /// <summary>
    /// Displays a loading dialog
    /// </summary>
    /// <param name="dialog">Instance of progress dialog (xamarin.forms)</param>
    public void ShowLoading(Dialogs.LoadingDialog dialog)
    
        if (Activity == null)
            return;

        DialogFragment frag = dialog.CreateDialogFragment(Activity);

        frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
        frag.Show(Activity.FragmentManager, "dialog");
        currentDialog = frag;
    

    /// <summary>
    /// Displays a prompt dialog
    /// </summary>
    /// <param name="dialog"></param>
    public void ShowPrompt(Dialogs.PromptDialog dialog)
    
        if (Activity == null)
            return;

        DialogFragment frag = dialog.CreateDialogFragment(Activity);

        frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
        frag.Show(Activity.FragmentManager, "dialog");
        currentDialog = frag;
    

    /// <summary>
    /// Hides loading dialog
    /// </summary>
    public void HideDialog()
    
        if (Activity == null)
            return;

        if (currentDialog != null)
        
            currentDialog.Dismiss();
            currentDialog = null;
        
    


请注意,您必须在调用实际方法以显示对话框之前为对话框服务设置活动,因此请确保在 MainActivity.cs 中调用

DialogService.Init(this);

在初始化 Xamarin.Forms 之后。

终于来了一些黑魔法:

通常,在 Android 中通过将片段容器放入主布局并在其中抛出片段来实现这样的对话框。不幸的是,由于使用 Xamarin.Forms,默认情况下这样的主布局不可用。

尽管 Xamarin.Forms 提供了一个视图扩展,它允许将 ContentPage 转换为片段 (ContentPage.CreateFragment),但我们不会成功使用它,因为它需要放置一个目标片段容器。

然而,android 提供了一个叫做 DialogFragment 的东西,它可以被扔到屏幕上,而不需要一个定义的片段容器。

遗憾的是,没有任何开箱即用的解决方案可用于从 Xamarin Forms 创建 DialogFragment。好消息是,这可以通过使用 System.Reflection(;) 的强大功能来克服,因此我们创建了自己的扩展方法和内部类的一些修改版本,xamarin.forms 在后台使用。为了实现这一点,我从 Xamarin.Platform.Android 中的 Xamarin.Forms 中获取代码并对其进行修改以从 ContentPage 创建一个 DialogFragment:

public static class PageExtensions

    public static DialogFragment CreateDialogFragment(this ContentPage view, Context context)
    
        if (!Forms.IsInitialized)
            throw new InvalidOperationException("call Forms.Init() before this");

        // Get Platform constructor via reflection and call it to create new platform object
        Platform platform = (Platform)typeof(Platform).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[]  typeof(Context), typeof(bool) , null)
            ?.Invoke(new object[]  context, true );

        // Set the page to the platform
        if (platform != null)
        
            platform.GetType().GetMethod("SetPage", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, new object[]  view );

            // Finally get the view group
            ViewGroup vg = (Android.Views.ViewGroup)platform.GetType().GetMethod("GetViewGroup", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, null);

            return new EmbeddedDialogFragment(vg, platform);
        

        return null;
    

    public class DefaultApplication : Xamarin.Forms.Application
    
    

    class EmbeddedDialogFragment : DialogFragment
    
        readonly ViewGroup _content;
        readonly Platform _platform;
        bool _disposed;

        public EmbeddedDialogFragment()
        
        

        public EmbeddedDialogFragment(ViewGroup content, Platform platform)
        
            _content = content;
            _platform = platform;
        

        public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        
            this.Dialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
            return _content;
        

        public override void OnDestroy()
        
            this.Dialog?.Window.SetSoftInputMode(SoftInput.AdjustPan);
            base.OnDestroy();
        

        protected override void Dispose(bool disposing)
        
            if (_disposed)
            
                return;
            

            _disposed = true;

            if (disposing)
            
                (_platform as IDisposable)?.Dispose();
            

            base.Dispose(disposing);
        
    

iOS 实现

幸运的是,对于 iOS 实现,无需深入研究 Xamarin.Forms 代码:

Here is the iOS implementation of DialogService:

[assembly: Dependency(typeof(DialogService))]
namespace BetterUI.iOS.Services

public class DialogService : IDialogs

    private UIViewController currentDialog;
    private UIWindow popupWindow = null;

    public void HideLoading()
    
        if (currentDialog != null)
        
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissModalViewController(false);
            currentDialog.Dispose();
            currentDialog = null;
        
    

    public bool IsDialogOpen()
    
        return (currentDialog != null && currentDialog.IsBeingPresented);
    

    public void ShowLoading(LoadingDialog dialog)
    
        UIViewController dialogController = dialog.CreateViewController();
        ShowDialog(dialogController);
        currentDialog = dialogController;

    

    public void ShowPrompt(PromptDialog dialog)
    
        UIViewController dialogController = dialog.CreateViewController();
        ShowDialog(dialogController);
        currentDialog = dialogController;
    

    private void ShowDialog(UIViewController dialogController)
    
        var bounds = UIScreen.MainScreen.Bounds;
        dialogController.View.Frame = bounds;
        UIApplication.SharedApplication.KeyWindow.RootViewController.ModalPresentationStyle = UIModalPresentationStyle.CurrentContext;
        UIApplication.SharedApplication.KeyWindow.RootViewController.AddChildViewController(dialogController);
        UIApplication.SharedApplication.KeyWindow.RootViewController.View.Opaque = false;
        UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.AllowsGroupOpacity = true;
        UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.BackgroundColor = new CGColor(Color.White.ToCGColor(), 0.0f);
        UIApplication.SharedApplication.KeyWindow.RootViewController.View.BackgroundColor = UIColor.Clear;
        UIApplication.SharedApplication.KeyWindow.RootViewController.View.AddSubview(dialogController.View);
        dialogController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
        dialogController.View.Opaque = false;
        dialogController.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.0f);
    


瞧,现在每当我们使用本文“方法”部分的调用时,都会显示一个包含我们自定义 Xamarin.Forms ContentPage 的漂亮弹出对话框。

【讨论】:

你能添加一些示例截图吗?大小如何处理?例如不同的内容大小,方向变化,...【参考方案4】:

使用包更容易,比如Plugins.Popup 来实现这一点,没有自定义渲染器,不可能在默认的AlertDialog中添加Image,这会限制你。

使用 Popup 插件,您只需将其添加到您的解决方案中,在 iOS 和 Android 中都进行初始化:

[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate

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

安卓:

namespace HelloXamarinFormsWorld.Android

    [Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true,
        ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
    
        protected override void OnCreate(Bundle bundle)
        
            base.OnCreate(bundle);

            Rg.Plugins.Popup.Popup.Init(this, bundle);

            Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication (new App ());
        
    

创建弹出页面

<pages:PopupPage.Animation>
    <animations:ScaleAnimation 
        PositionIn="Center"
        PositionOut="Center"
        ScaleIn="1.2"
        ScaleOut="0.8"
        DurationIn="400"
        DurationOut="300"
        EasingIn="SinOut"
        EasingOut="SinIn"
        HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout 
    VerticalOptions="Center" 
    HorizontalOptions="Center" 
    Padding="20, 20, 20, 20">
    <Label
        Text="Test"/>
</StackLayout>

并且,要显示在您的页面中:

await Navigation.PushPopupAsync(page);

【讨论】:

【参考方案5】:

使用名为“Rg.Plugin.Popup”的包。它会帮助你。

【讨论】:

Bruno Caceiro 已经在他的回答中提到了这一点并添加了解释。

以上是关于显示带有 xamarin 表单的弹出窗口的主要内容,如果未能解决你的问题,请参考以下文章

在 c# Xamarin 中等待带有弹出窗口的任务

使用带有 Xamarin 表单的 Rg 插件弹出窗口

我将如何设置 Xamarin Forms Android Picker 弹出窗口的背景颜色

Sencha touch 中类似 iPad 的弹出窗口

迫切需要 Xamarin.IOS 模态 MessageBox 之类的弹出窗口

kivy:带有进度条的弹出窗口