Xamarin.Forms 在 Xamarin 本机项目中

下载示例 下载示例

通常, Xamarin.Forms 应用程序包括派生自 ContentPage的一个或多个页面,这些页面由 .NET Standard 库项目或共享项目中的所有平台共享。 但是,本机窗体允许 ContentPage将 派生的页面直接添加到本机 Xamarin.iOS、Xamarin.Android 和 UWP 应用程序。 与让本机项目使用 ContentPage.NET Standard 库项目或共享项目中的派生页面相比,直接将页面添加到本机项目的优点是可以使用本机视图扩展页面。 然后,可以使用 在 XAML x:Name 中命名本机视图,并从代码隐藏中引用本机视图。 有关本机视图的详细信息,请参阅 本机视图

在本机项目中使用 Xamarin.FormsContentPage派生页的过程如下:

  1. 将 Xamarin.Forms NuGet 包添加到本机项目。
  2. ContentPage派生页和任何依赖项添加到本机项目。
  3. 调用 Forms.Init 方法。
  4. 使用以下扩展方法之一构造 派生页面的 ContentPage实例,并将其转换为适当的本机类型: CreateViewController 适用于 iOS、 CreateSupportFragment 适用于 Android 或 CreateFrameworkElement UWP。
  5. 使用本机导航 API 导航到 派生页面的 ContentPage本机类型表示形式。

Xamarin.Forms 必须先通过调用 方法进行初始化, Forms.Init 然后本机项目才能构造 ContentPage派生页。 选择何时执行此操作主要取决于它在应用程序流中何时最方便 - 它可以在应用程序启动时执行,也可以在构造 派生页之前 ContentPage执行。 在本文中以及随附的示例应用程序中, Forms.Init 方法在应用程序启动时调用。

注意

NativeForms 示例应用程序解决方案不包含任何Xamarin.Forms项目。 它由 Xamarin.iOS 项目、Xamarin.Android 项目和 UWP 项目组成。 每个项目都是一个本机项目,它使用本机窗体来使用 ContentPage派生页面。 但是,本机项目没有理由无法使用 ContentPage.NET Standard 库项目或共享项目中的派生页。

使用本机窗体、Xamarin.Forms、 MessagingCenterDependencyService功能以及数据绑定引擎时,仍可正常工作。 但是,必须使用本机导航 API 执行页面导航。

iOS

在 iOS 上 FinishedLaunching , 类中的 AppDelegate 替代通常是执行应用程序启动相关任务的位置。 它在应用程序启动后调用,通常被重写以配置main窗口和视图控制器。 下面的代码示例演示 AppDelegate 示例应用程序中的 类:

[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
    public static AppDelegate Instance;
    UIWindow _window;
    AppNavigationController _navigation;

    public static string FolderPath { get; private set; }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        Forms.Init();

        // Create app-level resource dictionary.
        Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
        Xamarin.Forms.Application.Current.Resources = new MyDictionary();

        Instance = this;
        _window = new UIWindow(UIScreen.MainScreen.Bounds);

        UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
        {
            TextColor = UIColor.Black
        });

        FolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));

        NotesPage notesPage = new NotesPage()
        {
            // Set the parent so that the app-level resource dictionary can be located.
            Parent = Xamarin.Forms.Application.Current
        };

        UIViewController notesPageController = notesPage.CreateViewController();
        notesPageController.Title = "Notes";

        _navigation = new AppNavigationController(notesPageController);

        _window.RootViewController = _navigation;
        _window.MakeKeyAndVisible();

        notesPage.Parent = null;
        return true;
    }
    // ...
}

FinishedLaunching 方法执行以下任务:

  • Xamarin.Forms 通过调用 Forms.Init 方法初始化。
  • 将创建一个新的 Xamarin.Forms.Application is 对象,并将其应用程序级资源字典设置为 ResourceDictionary 在 XAML 中定义的 。
  • 对 类的 AppDelegate 引用存储在 staticInstance 字段中。 这是为了为其他类提供一种机制来调用 类中 AppDelegate 定义的方法。
  • 创建 UIWindow,它是本机 iOS 应用程序中视图的main容器。
  • 属性 FolderPath 初始化为设备上将存储笔记数据的路径。
  • 创建一个 NotesPage 对象,该对象是在 Xamarin.FormsContentPageXAML 中定义的 派生页,其父级设置为以前创建 Xamarin.Forms.Application 的对象。
  • 使用 NotesPageCreateViewController 扩展方法将 对象转换为 UIViewController
  • Title设置 的 UIViewController 属性,该属性将显示在 上UINavigationBar
  • AppNavigationController创建 用于管理分层导航的 。 这是派生自 UINavigationController的自定义导航控制器类。 对象 AppNavigationController 管理视图控制器的堆栈,传入 UIViewController 构造函数的 将在加载 时 AppNavigationController 最初呈现。
  • 对象 AppNavigationController 设置为 的顶级 UIViewControllerUIWindow,将 UIWindow 设置为应用程序的键窗口,并可见。
  • 对象的 Parent 属性 NotesPage 设置为 null,以防止内存泄漏。

FinishedLaunching执行 方法后,将显示 类中Xamarin.FormsNotesPage定义的 UI,如以下屏幕截图所示:

屏幕截图显示移动设备上的“备注”屏幕。

重要

所有 ContentPage派生页都可以使用应用程序级别 ResourceDictionary中定义的资源,前提是 Parent 页面的 属性设置为 Application 对象。

与 UI 交互(例如通过点击 +Button)将导致代码隐藏中 NotesPage 执行以下事件处理程序:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    AppDelegate.Instance.NavigateToNoteEntryPage(new Note());
}

字段 staticAppDelegate.Instance 允许 AppDelegate.NavigateToNoteEntryPage 调用 方法,如以下代码示例所示:

public void NavigateToNoteEntryPage(Note note)
{
    NoteEntryPage noteEntryPage = new NoteEntryPage
    {
        BindingContext = note,
        // Set the parent so that the app-level resource dictionary can be located.
        Parent = Xamarin.Forms.Application.Current
    };

    var noteEntryViewController = noteEntryPage.CreateViewController();
    noteEntryViewController.Title = "Note Entry";

    _navigation.PushViewController(noteEntryViewController, true);
    noteEntryPage.Parent = null;
}

方法 NavigateToNoteEntryPage 使用 扩展方法将 Xamarin.FormsContentPage派生页转换为 UIViewControllerCreateViewController ,并设置 TitleUIViewController属性。 UIViewController然后, 由 PushViewController 方法推送到 上AppNavigationController。 因此,将显示 类中 Xamarin.FormsNoteEntryPage 定义的 UI,如以下屏幕截图所示:

屏幕截图显示移动设备上的“注释条目”。

NoteEntryPage显示 时,后退导航将从 弹出UIViewControllerAppNavigationControllerNoteEntryPage ,并将用户返回到UIViewController类的 NotesPage 。 但是,从 iOS 本机导航堆栈中弹出 UIViewController 不会自动释放 UIViewController 和 附加 Page 的对象。 因此, AppNavigationController 类重写 PopViewController 方法,以在向后导航上释放视图控制器:

public class AppNavigationController : UINavigationController
{
    //...
    public override UIViewController PopViewController(bool animated)
    {
        UIViewController topView = TopViewController;
        if (topView != null)
        {
            // Dispose of ViewController on back navigation.
            topView.Dispose();
        }
        return base.PopViewController(animated);
    }
}

重写 PopViewController 对从 iOS 本机导航堆栈弹出的对象调用 Dispose 方法 UIViewController 。 如果不这样做, UIViewController 将导致 和 附加 Page 的对象成为孤立对象。

重要

无法对孤立对象进行垃圾回收,因此会导致内存泄漏。

Android

在 Android 上 OnCreate , 类中的 MainActivity 替代通常是执行应用程序启动相关任务的位置。 下面的代码示例演示 MainActivity 示例应用程序中的 类:

public class MainActivity : AppCompatActivity
{
    public static string FolderPath { get; private set; }

    public static MainActivity Instance;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Forms.Init(this, bundle);

        // Create app-level resource dictionary.
        Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
        Xamarin.Forms.Application.Current.Resources = new MyDictionary();

        Instance = this;

        SetContentView(Resource.Layout.Main);
        var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
        SetSupportActionBar(toolbar);
        SupportActionBar.Title = "Notes";

        FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));

        NotesPage notesPage = new NotesPage()
        {
            // Set the parent so that the app-level resource dictionary can be located.
            Parent = Xamarin.Forms.Application.Current
        };
        AndroidX.Fragment.App.Fragment notesPageFragment = notesPage.CreateSupportFragment(this);

        SupportFragmentManager
            .BeginTransaction()
            .Replace(Resource.Id.fragment_frame_layout, mainPage)
            .Commit();
        //...

        notesPage.Parent = null;
    }
    ...
}

OnCreate 方法执行以下任务:

  • Xamarin.Forms 通过调用 Forms.Init 方法初始化。
  • 将创建一个新的 Xamarin.Forms.Application is 对象,并将其应用程序级资源字典设置为 ResourceDictionary 在 XAML 中定义的 。
  • 对 类的 MainActivity 引用存储在 staticInstance 字段中。 这是为了为其他类提供一种机制来调用 类中 MainActivity 定义的方法。
  • 内容 Activity 是从布局资源设置的。 在示例应用程序中,布局由 LinearLayout 包含 Toolbar的 和 FrameLayout 作为片段容器的 组成。
  • Toolbar检索并设置为 的操作栏Activity,并设置操作栏标题。
  • 属性 FolderPath 初始化为设备上将存储笔记数据的路径。
  • 创建一个 NotesPage 对象,该对象是在 Xamarin.FormsContentPageXAML 中定义的 派生页,其父级设置为以前创建 Xamarin.Forms.Application 的对象。
  • 使用 NotesPageCreateSupportFragment 扩展方法将 对象转换为 Fragment
  • SupportFragmentManager 创建并提交一个事务,该事务将 FrameLayout 实例替换为 Fragment 类的 NotesPage
  • 对象的 Parent 属性 NotesPage 设置为 null,以防止内存泄漏。

有关片段的详细信息,请参阅 片段

OnCreate执行 方法后,将显示 类中Xamarin.FormsNotesPage定义的 UI,如以下屏幕截图所示:

屏幕截图显示移动设备上带有蓝色横幅和彩色笔记文本的“备注”屏幕。

重要

所有 ContentPage派生页都可以使用应用程序级别 ResourceDictionary中定义的资源,前提是 Parent 页面的 属性设置为 Application 对象。

与 UI 交互(例如通过点击 +Button)将导致代码隐藏中 NotesPage 执行以下事件处理程序:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    MainActivity.Instance.NavigateToNoteEntryPage(new Note());
}

字段 staticMainActivity.Instance 允许 MainActivity.NavigateToNoteEntryPage 调用 方法,如以下代码示例所示:

public void NavigateToNoteEntryPage(Note note)
{
    NoteEntryPage noteEntryPage = new NoteEntryPage
    {
        BindingContext = note,
        // Set the parent so that the app-level resource dictionary can be located.
        Parent = Xamarin.Forms.Application.Current
    };

    AndroidX.Fragment.App.Fragment noteEntryFragment = noteEntryPage.CreateSupportFragment(this);
    SupportFragmentManager
        .BeginTransaction()
        .AddToBackStack(null)
        .Replace(Resource.Id.fragment_frame_layout, noteEntryFragment)
        .Commit();

    noteEntryPage.Parent = null;
}

方法NavigateToNoteEntryPage使用 扩展方法将 Xamarin.FormsContentPage派生页FragmentCreateSupportFragment转换为 ,并将 添加到Fragment片段后退堆栈。 因此,将显示 中 Xamarin.FormsNoteEntryPage 定义的 UI,如以下屏幕截图所示:

屏幕截图显示移动设备上带有蓝色横幅的备注条目。

NoteEntryPage显示 时,点击后退箭头将从片段返回堆栈中弹出 FragmentNoteEntryPage 的 ,并将用户返回到 Fragment 类的 NotesPage

启用后退导航支持

SupportFragmentManager 具有一个 BackStackChanged 事件,每当片段回退堆栈的内容发生更改时,该事件将触发。 OnCreate类中的 MainActivity 方法包含此事件的匿名事件处理程序:

SupportFragmentManager.BackStackChanged += (sender, e) =>
{
    bool hasBack = SupportFragmentManager.BackStackEntryCount > 0;
    SupportActionBar.SetHomeButtonEnabled(hasBack);
    SupportActionBar.SetDisplayHomeAsUpEnabled(hasBack);
    SupportActionBar.Title = hasBack ? "Note Entry" : "Notes";
};

此事件处理程序在操作栏上显示一个后退按钮,前提是片段后退堆栈上有一个或多个 Fragment 实例。 点击后退按钮的响应由 OnOptionsItemSelected 替代处理:

public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)
{
    if (item.ItemId == global::Android.Resource.Id.Home && SupportFragmentManager.BackStackEntryCount > 0)
    {
        SupportFragmentManager.PopBackStack();
        return true;
    }
    return base.OnOptionsItemSelected(item);
}

OnOptionsItemSelected每当选择选项菜单中的项时,将调用替代。 此实现从片段后退堆栈弹出当前片段,前提是已选择“后退”按钮,并且片段后退堆栈上存在一个或多个 Fragment 实例。

多个活动

当应用程序由多个活动组成时, ContentPage派生页可以嵌入到每个活动中。 在此方案中,Forms.Init只需在嵌入 的第一Activity个 Xamarin.FormsContentPage的 重写中OnCreate调用 方法。 但是,这具有以下影响:

  • 的值 Xamarin.Forms.Color.Accent 取自 Activity 调用 方法的 Forms.Init
  • 的值Xamarin.Forms.Application.Current将与调用 方法的 Forms.Init 相关联Activity

选择文件

嵌入 ContentPage派生的页面时,使用 WebView 需要支持 HTML“选择文件”按钮的 时, Activity 需要重写 OnActivityResult 方法:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    ActivityResultCallbackRegistry.InvokeCallback(requestCode, resultCode, data);
}

UWP

在 UWP 上,本机 App 类通常是执行应用程序启动相关任务的位置。 Xamarin.Forms通常在 UWP 应用程序中,在Xamarin.Forms本机App类的 重写中OnLaunched初始化,以将 LaunchActivatedEventArgs 参数传递给 Forms.Init 方法。 因此,使用 Xamarin.FormsContentPage派生页面的本机 UWP 应用程序最容易地从 App.OnLaunched 方法调用 Forms.Init 方法:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    // ...
    Xamarin.Forms.Forms.Init(e);

    // Create app-level resource dictionary.
    Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
    Xamarin.Forms.Application.Current.Resources = new MyDictionary();

    // ...
}

此外, OnLaunched 方法还可以创建应用程序所需的任何应用程序级资源字典。

默认情况下,本机 App 类启动 MainPage 类作为应用程序的第一页。 下面的代码示例演示 MainPage 示例应用程序中的 类:

public sealed partial class MainPage : Page
{
    NotesPage notesPage;
    NoteEntryPage noteEntryPage;

    public static MainPage Instance;
    public static string FolderPath { get; private set; }

    public MainPage()
    {
        this.NavigationCacheMode = NavigationCacheMode.Enabled;
        Instance = this;
        FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));

        notesPage = new Notes.UWP.Views.NotesPage
        {
            // Set the parent so that the app-level resource dictionary can be located.
            Parent = Xamarin.Forms.Application.Current
        };
        this.Content = notesPage.CreateFrameworkElement();
        // ...
        notesPage.Parent = null;    
    }
    // ...
}

构造 MainPage 函数执行以下任务:

  • 为页面启用缓存,以便在用户导航回页面时不会构造新的 MainPage
  • 对 类的 MainPage 引用存储在 staticInstance 字段中。 这是为了为其他类提供一种机制来调用 类中 MainPage 定义的方法。
  • 属性 FolderPath 初始化为设备上将存储笔记数据的路径。
  • 创建一个 NotesPage 对象,该对象是在 Xamarin.FormsContentPageXAML 中定义的 派生页,其父级设置为以前创建 Xamarin.Forms.Application 的对象。
  • 使用 NotesPageCreateFrameworkElement 扩展方法将 对象转换为 FrameworkElement ,然后将 设置为 类的内容MainPage
  • 对象的 Parent 属性 NotesPage 设置为 null,以防止内存泄漏。

MainPage执行构造函数后,将显示 类中Xamarin.FormsNotesPage定义的 UI,如以下屏幕截图所示:

屏幕截图显示包含备注和日期/时间的“备注”页。

重要

所有 ContentPage派生页都可以使用应用程序级别 ResourceDictionary中定义的资源,前提是 Parent 页面的 属性设置为 Application 对象。

与 UI 交互(例如通过点击 +Button)将导致代码隐藏中 NotesPage 执行以下事件处理程序:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    MainPage.Instance.NavigateToNoteEntryPage(new Note());
}

字段 staticMainPage.Instance 允许 MainPage.NavigateToNoteEntryPage 调用 方法,如以下代码示例所示:

public void NavigateToNoteEntryPage(Note note)
{
    noteEntryPage = new Notes.UWP.Views.NoteEntryPage
    {
        BindingContext = note,
        // Set the parent so that the app-level resource dictionary can be located.
        Parent = Xamarin.Forms.Application.Current
    };
    this.Frame.Navigate(noteEntryPage);
    noteEntryPage.Parent = null;
}

UWP 中的导航通常使用 Frame.Navigate 方法执行,该方法采用 Page 参数。 Xamarin.Forms 定义采用 Frame.Navigate 派生页实例的 ContentPage扩展方法。 因此,执行 方法时 NavigateToNoteEntryPage ,将显示 中 Xamarin.FormsNoteEntryPage 定义的 UI,如以下屏幕截图所示:

屏幕截图显示了一个“备注”页,其中包含一个文本框,其中输入了备注。

NoteEntryPage显示 时,点击后退箭头将从应用内返回堆栈中弹出 FrameworkElementNoteEntryPage 的 ,并将用户返回到 FrameworkElement 类的 NotesPage

启用页面大小调整支持

调整 UWP 应用程序窗口的大小时, Xamarin.Forms 还应调整内容的大小。 这是通过在 构造函数中为 Loaded 事件注册事件处理程序来实现的 MainPage

public MainPage()
{
    // ...
    this.Loaded += OnMainPageLoaded;
    // ...
}

当页面布局、呈现并准备好交互时,将 Loaded 触发 事件,并在响应中执行 OnMainPageLoaded 方法:

void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
    this.Frame.SizeChanged += (o, args) =>
    {
        if (noteEntryPage != null)
            noteEntryPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
        else
            notesPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
    };
}

方法OnMainPageLoadedFrame.SizeChanged 事件注册匿名事件处理程序,当 或 ActualWidth 上的 属性发生更改时ActualHeightFrame将引发该事件。 作为响应, Xamarin.Forms 通过调用 Layout 方法调整活动页面的内容大小。

启用后退导航支持

在 UWP 上,应用程序必须跨不同的设备外形规格为所有硬件和软件后退按钮启用后退导航。 这可以通过为 事件注册事件处理程序来实现,该事件处理程序 BackRequested 可在构造函数中 MainPage 执行:

public MainPage()
{
    // ...
    SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}

启动应用程序时, GetForCurrentView 方法检索与 SystemNavigationManager 当前视图关联的对象,然后为 BackRequested 事件注册事件处理程序。 仅当应用程序是前台应用程序时,应用程序才会接收此事件,并在响应中调用 OnBackRequested 事件处理程序:

void OnBackRequested(object sender, BackRequestedEventArgs e)
{
    Frame rootFrame = Window.Current.Content as Frame;
    if (rootFrame.CanGoBack)
    {
        e.Handled = true;
        rootFrame.GoBack();
        noteEntryPage = null;
    }
}

事件处理程序 OnBackRequested 在应用程序的根帧上调用 GoBack 方法,并将 属性设置为 BackRequestedEventArgs.Handledtrue 以将事件标记为已处理。 未能将事件标记为已处理可能会导致事件被忽略。

应用程序选择是否在标题栏上显示后退按钮。 这是通过在 类中将 AppViewBackButtonVisibility 属性设置为枚举值之 AppViewBackButtonVisibility 一来实现的 App

void OnNavigated(object sender, NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
        ((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}

OnNavigated事件处理程序(为响应Navigated事件触发而执行)在发生页面导航时更新标题栏后退按钮的可见性。 这可确保在应用内后退堆栈不为空时显示标题栏后退按钮;如果应用内后退堆栈为空,则从标题栏中删除。

有关 UWP 上的后退导航支持的详细信息,请参阅 UWP 应用的导航历史记录和向后导航