Xamarin.Forms 在 Xamarin 本机项目中
通常, Xamarin.Forms 应用程序包括派生自 ContentPage
的一个或多个页面,这些页面由 .NET Standard 库项目或共享项目中的所有平台共享。 但是,本机窗体允许 ContentPage
将 派生的页面直接添加到本机 Xamarin.iOS、Xamarin.Android 和 UWP 应用程序。 与让本机项目使用 ContentPage
.NET Standard 库项目或共享项目中的派生页面相比,直接将页面添加到本机项目的优点是可以使用本机视图扩展页面。 然后,可以使用 在 XAML x:Name
中命名本机视图,并从代码隐藏中引用本机视图。 有关本机视图的详细信息,请参阅 本机视图。
在本机项目中使用 Xamarin.FormsContentPage
派生页的过程如下:
- 将 Xamarin.Forms NuGet 包添加到本机项目。
- 将
ContentPage
派生页和任何依赖项添加到本机项目。 - 调用
Forms.Init
方法。 - 使用以下扩展方法之一构造 派生页面的
ContentPage
实例,并将其转换为适当的本机类型:CreateViewController
适用于 iOS、CreateSupportFragment
适用于 Android 或CreateFrameworkElement
UWP。 - 使用本机导航 API 导航到 派生页面的
ContentPage
本机类型表示形式。
Xamarin.Forms 必须先通过调用 方法进行初始化, Forms.Init
然后本机项目才能构造 ContentPage
派生页。 选择何时执行此操作主要取决于它在应用程序流中何时最方便 - 它可以在应用程序启动时执行,也可以在构造 派生页之前 ContentPage
执行。 在本文中以及随附的示例应用程序中, Forms.Init
方法在应用程序启动时调用。
注意
NativeForms 示例应用程序解决方案不包含任何Xamarin.Forms项目。 它由 Xamarin.iOS 项目、Xamarin.Android 项目和 UWP 项目组成。 每个项目都是一个本机项目,它使用本机窗体来使用 ContentPage
派生页面。 但是,本机项目没有理由无法使用 ContentPage
.NET Standard 库项目或共享项目中的派生页。
使用本机窗体、Xamarin.Forms、 MessagingCenter
等DependencyService
功能以及数据绑定引擎时,仍可正常工作。 但是,必须使用本机导航 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
引用存储在static
Instance
字段中。 这是为了为其他类提供一种机制来调用 类中AppDelegate
定义的方法。 - 创建
UIWindow
,它是本机 iOS 应用程序中视图的main容器。 - 属性
FolderPath
初始化为设备上将存储笔记数据的路径。 - 创建一个
NotesPage
对象,该对象是在 Xamarin.FormsContentPage
XAML 中定义的 派生页,其父级设置为以前创建Xamarin.Forms.Application
的对象。 - 使用
NotesPage
CreateViewController
扩展方法将 对象转换为UIViewController
。 Title
设置 的UIViewController
属性,该属性将显示在 上UINavigationBar
。AppNavigationController
创建 用于管理分层导航的 。 这是派生自UINavigationController
的自定义导航控制器类。 对象AppNavigationController
管理视图控制器的堆栈,传入UIViewController
构造函数的 将在加载 时AppNavigationController
最初呈现。- 对象
AppNavigationController
设置为 的顶级UIViewController
UIWindow
,将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());
}
字段 static
AppDelegate.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
派生页转换为 UIViewController
CreateViewController
,并设置 Title
的 UIViewController
属性。 UIViewController
然后, 由 PushViewController
方法推送到 上AppNavigationController
。 因此,将显示 类中 Xamarin.FormsNoteEntryPage
定义的 UI,如以下屏幕截图所示:
NoteEntryPage
显示 时,后退导航将从 弹出UIViewController
类AppNavigationController
的 NoteEntryPage
,并将用户返回到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
引用存储在static
Instance
字段中。 这是为了为其他类提供一种机制来调用 类中MainActivity
定义的方法。 - 内容
Activity
是从布局资源设置的。 在示例应用程序中,布局由LinearLayout
包含Toolbar
的 和FrameLayout
作为片段容器的 组成。 Toolbar
检索并设置为 的操作栏Activity
,并设置操作栏标题。- 属性
FolderPath
初始化为设备上将存储笔记数据的路径。 - 创建一个
NotesPage
对象,该对象是在 Xamarin.FormsContentPage
XAML 中定义的 派生页,其父级设置为以前创建Xamarin.Forms.Application
的对象。 - 使用
NotesPage
CreateSupportFragment
扩展方法将 对象转换为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());
}
字段 static
MainActivity.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
派生页Fragment
CreateSupportFragment
转换为 ,并将 添加到Fragment
片段后退堆栈。 因此,将显示 中 Xamarin.FormsNoteEntryPage
定义的 UI,如以下屏幕截图所示:
NoteEntryPage
显示 时,点击后退箭头将从片段返回堆栈中弹出 Fragment
NoteEntryPage
的 ,并将用户返回到 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
引用存储在static
Instance
字段中。 这是为了为其他类提供一种机制来调用 类中MainPage
定义的方法。 - 属性
FolderPath
初始化为设备上将存储笔记数据的路径。 - 创建一个
NotesPage
对象,该对象是在 Xamarin.FormsContentPage
XAML 中定义的 派生页,其父级设置为以前创建Xamarin.Forms.Application
的对象。 - 使用
NotesPage
CreateFrameworkElement
扩展方法将 对象转换为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());
}
字段 static
MainPage.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
显示 时,点击后退箭头将从应用内返回堆栈中弹出 FrameworkElement
NoteEntryPage
的 ,并将用户返回到 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));
};
}
方法OnMainPageLoaded
为 Frame.SizeChanged
事件注册匿名事件处理程序,当 或 ActualWidth
上的 属性发生更改时ActualHeight
,Frame
将引发该事件。 作为响应, 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.Handled
true
以将事件标记为已处理。 未能将事件标记为已处理可能会导致事件被忽略。
应用程序选择是否在标题栏上显示后退按钮。 这是通过在 类中将 AppViewBackButtonVisibility
属性设置为枚举值之 AppViewBackButtonVisibility
一来实现的 App
:
void OnNavigated(object sender, NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}
OnNavigated
事件处理程序(为响应Navigated
事件触发而执行)在发生页面导航时更新标题栏后退按钮的可见性。 这可确保在应用内后退堆栈不为空时显示标题栏后退按钮;如果应用内后退堆栈为空,则从标题栏中删除。
有关 UWP 上的后退导航支持的详细信息,请参阅 UWP 应用的导航历史记录和向后导航。