Xamarin.Forms 在设计上借鉴了基于 XAML 的框架,特别是 WPF,但也在某些方面存在显著差异,这可能对迁移者构成挑战。本文旨在指出这些差异,并提供将 WPF 知识应用于 Xamarin.Forms 的指导。
应用生命周期
WPF 和 Xamarin.Forms 的应用程序生命周期类似,两者都从外部平台代码开始,并通过方法调用启动 UI。区别在于,Xamarin.Forms 始终在特定于平台的程序集中启动,然后初始化并创建应用的 UI。
在 WPF 中,生命周期通常遵循 Main method > App > MainWindow 的顺序。注意,默认情况下 Main 方法在代码中自动生成且不可见。
对于 Xamarin.Forms,不同平台有不同路径:
- iOS:
Main method > AppDelegate > App > ContentPage - Android:
MainActivity > App > ContentPage - UWP:
Main method > App(UWP) > MainPage(UWP) > App > ContentPage
应用程序类
WPF 和 Xamarin.Forms 都有一个 Application 类,该类创建为单一实例。在大多数情况下,应用将派生自此类以提供自定义应用程序,尽管 WPF 中并不严格要求这样做。两者都公开 Application.Current 属性以查找创建的单一实例。
全局属性与持久性
WPF 和 Xamarin.Forms 都有一个 Application.Properties 字典,可在其中存储全局应用级对象,以便在应用程序中的任意位置访问它们。主要区别在于,Xamarin.Forms 将在应用暂停时保留集合中存储的任何基元类型,并在重新启动时重新加载它们。WPF 不会自动支持该行为,相反,大多数开发人员依赖于独立存储或使用内置的 Settings 支持。
定义页面和可视化树
WPF 将 Window 用作任何顶级视觉元素的根元素,这在 Windows 世界中定义用于显示信息的 HWND。可以像在 WPF 中一样同时创建和显示任意数量的窗口。
在 Xamarin.Forms 中,顶级视觉对象始终由平台定义,例如在 iOS 上,它是 UIWindow。Xamarin.Forms 使用 Page 类将其内容呈现到这些本机平台表示形式中。每个 Page 都表示应用程序中唯一的“页面”,一次只显示一个。
WPF Window 和 Xamarin.Forms Page 都包含一个用于影响显示标题的 Title 属性,并且都有一个 Icon 属性来显示页面的特定图标(注意,标题和图标并不总是在 Xamarin.Forms 中可见)。此外,还可以更改两者的常见视觉属性,例如背景色或图像。
从技术上看,可以呈现到两个单独的平台视图(例如,定义两个 UIWindow 对象,并将第二个呈现到外部显示器或 AirPlay),但这需要特定于平台的代码,不是 Xamarin.Forms 本身直接支持的功能。
视图
这两个框架的视觉层次结构相似。由于 WPF 对 WYSIWYG 文档的支持,其层次结构更深一些。
WPF 的视觉层次结构:
DependencyObject - base class for all bindable things
Visual - rendering mechanics
UIElement - common events + interactions
FrameworkElement - adds layout
Shape - 2D graphics
Control - interactive controls
Xamarin.Forms 的视觉层次结构:
BindableObject - base class for all bindable things
Element - basic parent/child support + resources + effects
VisualElement - adds visual rendering properties (color, fonts, transforms, etc.)
View - layout + gesture support
视图生命周期
Xamarin.Forms 主要面向移动方案,因此当用户与应用程序交互时,应用程序会激活、暂停和重新激活。这类似于在 WPF 应用程序中单击 Window 以外的区域,并且有一组方法和相应的事件可以替代或挂钩以监视此行为。
以下表格对比了视图生命周期方法:
| 目的 | WPF 方法 | Xamarin.Forms 方法 |
|---|---|---|
| 初始激活 | ctor + Window.OnLoaded | ctor + Page.OnStart |
| 显示 | Window.IsVisibleChanged | Page.Appearing |
| 已隐藏 | Window.IsVisibleChanged | Page.Disappearing |
| 暂停/丢失焦点 | Window.OnDeactivated | Page.OnSleep |
| 激活/获取焦点 | Window.OnActivated | Page.OnResume |
| 已解决 | Window.OnClosing + Window.OnClosed | 不适用 |
两者都支持隐藏/显示子控件。在 WPF 中,它是一个三态属性 IsVisible(可见、隐藏和折叠)。在 Xamarin.Forms 中,它只通过 IsVisible 属性可见或隐藏。
布局
页面布局发生在 WPF 中发生的同一 2 次传递(度量/排列)中。可以通过替代 Xamarin.Forms Page 类中的以下方法来钩入页面布局:
- OnChildMeasureInvalidated:子级的首选大小已更改。
- OnSizeAllocated:已为页面分配宽度/高度。
- LayoutChanged 事件:页面的布局/大小已更改。
没有今天调用的全局布局事件,也没有像在 WPF 中找到的全局 CompositionTarget.Rendering 事件。
常见布局属性
WPF 和 Xamarin.Forms 都支持 Margin 来控制元素周围的间距,并支持 Padding 来控制元素内的间距。此外,大多数 Xamarin.Forms 布局视图都具有控制间距的属性(例如行或列)。
大多数元素都具有属性来影响它们在父容器中的定位方式:
| WPF | Xamarin.Forms | 目的 |
|---|---|---|
| HorizontalAlignment | HorizontalOptions | Left/Center/Right/Stretch 选项 |
| VerticalAlignment | VerticalOptions | Top/Center/Bottom/Stretch 选项 |
布局视图
WPF 和 Xamarin.Forms 都使用布局控件来定位子元素,在大多数情况下,它们在功能方面彼此非常接近。
| WPF | Xamarin.Forms | 布局样式 |
|---|---|---|
| StackPanel | StackLayout | 从左到右,或从上到下无限堆叠 |
| Grid | 网格 | 表格格式(行和列) |
| DockPanel | 不适用 | 停靠到窗口边缘 |
| 画布 | AbsoluteLayout | 像素/坐标定位 |
| WrapPanel | 不适用 | 包装堆栈 |
| 不适用 | RelativeLayout | 基于规则的相对定位 |
注意:Xamarin.Forms 不支持 GridSplitter。这两个平台都使用附加属性来微调子级。
渲染
WPF 和 Xamarin.Forms 的呈现机制截然不同。在 WPF 中,你创建的控件将内容直接呈现到屏幕上的像素。WPF 有两个对象图(树)来表示这一点:逻辑树表示代码或 XAML 中定义的控件,可视化树表示由视觉元素直接执行的或通过 XAML 定义的 ControlTemplate 执行的实际呈现。通常,可视化树更为复杂,因为它包括控件周围的边框、隐式内容的标签等。WPF 包含一组 API(LogicalTreeHelper 和 VisualTreeHelper)来检查这两个对象图。
在 Xamarin.Forms 中,你在 Page 中定义的控件实际上只是简单的数据对象,它们类似于逻辑树表示形式,但绝不会自行呈现内容。相反,它们是影响元素呈现的 数据模型。实际的呈现由一组单独的视觉呈现器完成,这些呈现器映射到每个控件类型。这些呈现器通过特定于平台的 Xamarin.Forms 程序集在每个特定于平台的项目中注册。除了替换或扩展呈现器之外,Xamarin.Forms 还支持效果,它可用于在每个平台上影响本机呈现。
逻辑/可视化树
没有公开的 API 可在 Xamarin.Forms 中遍历逻辑树,但你可以使用反射来获取相同的信息。例如,有一些方法使用反射来枚举逻辑子级。
图形
Xamarin.Forms 包含用于绘制基元的图形系统,称为 Shapes。此外,还可以包含第三方库(如 SkiaSharp)以获取跨平台 2D 绘图。
资源
WPF 和 Xamarin.Forms 都有资源和资源字典的概念。可以将任何对象类型放入具有键的某个 ResourceDictionary,然后使用 {StaticResource} 查找其中不会更改的项,或者使用 {DynamicResource} 查找在运行时可在字典中更改的项。用法和机制是相同的,但有一个区别:Xamarin.Forms 要求你定义 ResourceDictionary 以分配给 Resources 属性,而 WPF 会预先创建一个并为你分配它。
例如,在 WPF 中:
<Window.Resources>
<Color x:Key="redColor">#ff0000</Color>
...
</Window.Resources>
在 Xamarin.Forms 中:
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="redColor">#ff0000</Color>
...
</ResourceDictionary>
</ContentPage.Resources>
如果未定义 ResourceDictionary,则会生成运行时错误。
样式
Xamarin.Forms 也完全支持样式,它可用于为构成 UI 的 Xamarin.Forms 元素设置主题。它们支持触发器(属性、事件和数据)、通过 BasedOn 继承以及值的资源查找。样式通过 Style 属性显式应用于元素,或者不提供资源键而隐式应用(就像 WPF 一样)。
设备样式
WPF 有一组预定义的属性(作为静态值存储在一组静态类上,例如 SystemColors),这些属性以值和资源键的形式指示系统颜色、字体和指标。Xamarin.Forms 也是类似,但它定义了一组设备样式来表示相同的内容。这些样式由框架提供,并基于运行时环境(例如辅助功能)设置值。
例如,在 WPF 中:
<Label Text="Title" Foreground="{DynamicResource {x:Static SystemColors.DesktopBrushKey}}" />
在 Xamarin.Forms 中:
<Label Text="Title" Style="{DynamicResource TitleStyle}" />