Xamarin.Forms 是一个帮助开发者快速创建跨平台 UI 的框架。它为 iOS、Android 和 Windows Phone 上的原生控件提供了一层抽象,这意味着应用程序可以共享大部分 UI 代码,同时保持各平台的外观样式。
Xamarin.Forms 使用 C# 编写,适用于快速开发复杂的应用程序。由于基于 Xamarin.Forms 的应用程序是原生应用程序,因此不受其他开发工具包的限制,如浏览器沙箱、有限的 API 或性能问题。使用 Xamarin.Forms 编写的程序能够利用底层平台的任何 API 或特性,例如 iOS 的 CoreMotion、PassKit 和 StoreKit;Android 的 NFC 和 Google Play Services;Windows Phone 的 Tiles。这意味着你可以开发应用程序,部分使用 Xamarin.Forms 创建 UI,部分使用原生 UI 开发包。
Xamarin.Forms 应用程序和传统跨平台应用的架构相似。最常见的方法是使用 Portable Libraries 或 Shared Projects 来共享代码,然后创建平台相关项目来使用共享代码。
Xamarin.Forms 使用两种方式创建用户界面:第一种是用 Xamarin.Forms 提供的 API 直接在源代码中创建 UI;另一种是使用 Extensible Application Markup Language (XAML),这是一种用于定义界面的标记语言。用户界面在 XML 文件中用 XAML 定义,运行时行为则在单独的代码文件中定义。
本指南介绍 Xamarin.Forms 框架基础,包含以下主题:安装 Xamarin.Forms、在 Visual Studio 或 Xamarin Studio 中新建解决方案、如何使用 Xamarin.Forms 中的页面和控件、如何在页面间导航、如何设置数据绑定。
要求
Xamarin.Forms 应用程序可以运行在以下移动操作系统中:
- Android 4.0 或以上
- iOS 6.1 或以上
- Windows Phone 8(使用 Visual Studio)
Xamarin.Forms 部分控件(如 DatePicker)需要安装 Windows Phone Toolkit。假定开发者已熟悉 Portable Class Libraries 和 Shared Projects 用法。
Mac 系统要求
在 macOS 上开发 Xamarin.Forms 程序需要安装 Xamarin Studio 5。开发 iOS 程序要求安装 Xcode 5(操作系统要求 OS X 10.8 或以上)。Windows Phone 程序无法在 macOS 上开发;IDE 也无法创建包含 Windows Phone 的项目模板。
Windows 系统要求
Xamarin.Forms 应用程序可以使用 Visual Studio 2012 或更新版本开发。
- PCL 解决方案模板需要 .NET 4.5 上的 Profile 78(Visual Studio 2012 可安装 .NET 4.5,Visual Studio 2013 已内置)。同时需要安装 Windows Phone SDK,否则会发生“Project type not supported”错误。
- Shared Project 项目模板需要安装 Visual Studio 2013 Update 2。
开始使用 Xamarin.Forms
Xamarin.Forms 以 .NET Portable Class Library (PCL) 形式实现,方便在多平台共享 API。创建应用程序的第一步是创建解决方案文件。一个 Xamarin.Forms 解决方案通常由以下项目组成:
- Portable Library:包含所有共享 UI 和其他共享代码的类库。
- Xamarin.Android Application:包含 Android 相关代码和 Android 版应用程序的入口点。
- Xamarin.iOS Application:包含 iOS 相关代码和 iOS 版应用程序的入口点。
- Windows Phone Application:包含 Windows Phone 相关代码和 Windows Phone 版应用程序的入口点。
Xamarin 3.0 提供的解决方案模板可创建 Xamarin.Forms 程序所需的所有项目。在 Xamarin Studio 中,选择 File > New > Solution,在对话框中选择 C# > Mobile Apps,然后选择 Blank App (Xamarin.Forms Portable)。输入项目名并按 OK,模板会创建一个包含三个项目的解决方案。使用 Xamarin Studio 创建的解决方案不包含 Windows Phone 项目;使用 Visual Studio 新建则支持 iOS、Android 和 Windows Phone。注意 Xamarin Studio 不支持创建 Windows Phone 应用程序,但可以打开包含 Windows Phone 项目的解决方案(如 Visual Studio 创建的解决方案),可以查看代码但无法编译和部署。
查看一个 Xamarin.Forms 应用程序
默认模板创建最简单的 Xamarin.Forms 应用程序。运行后,你会看到一个使用 Xamarin.Forms Page 的界面。例如,HelloXamarinFormsWorld 使用一个 Xamarin.Forms.ContentPage 对象展示一个标签。为了最大化重用启动代码,Xamarin.Forms 应用程序创建一个叫 App 的类,用于实例化第一个展示的 Page 对象。例如:
public class App
{
public static Page GetMainPage()
{
return new ContentPage
{
Content = new Label
{
Text = "Hello, Forms !",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
},
};
}
}
上述代码实例化一个 ContentPage 对象,显示一个单行 Label,并在页面中水平和垂直居中。
在各平台启动初始化 Xamarin.Forms Page
不同平台上的启动代码不同,用于初始化 Xamarin.Forms 框架并提供初始 Page 实例。
Android
在 Android 中,创建一个使用 MainLauncher 特性的 Activity,继承 Xamarin.Forms.Platform.Android.AndroidActivity,重写 OnCreate 方法初始化 Xamarin.Forms 框架并显示初始 Page。例如:
namespace HelloXamarinFormsWorld.Android
{
[Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true)]
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
SetPage(App.GetMainPage());
}
}
}
iOS
在 Xamarin.iOS 应用程序中,在 AppDelegate 类的 FinishedLaunching 方法中初始化 Xamarin.Forms 框架,并设置 RootViewController 为初始 Page。例如:
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Forms.Init();
window = new UIWindow(UIScreen.MainScreen.Bounds);
window.RootViewController = App.GetMainPage().CreateViewController();
window.MakeKeyAndVisible();
return true;
}
}
Windows Phone
在 Windows Phone 项目中,初始页初始化 Xamarin.Forms 框架,并用 Xamarin.Forms Page 对象设置起始页内容。例如:
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
Forms.Init();
Content = HelloXamarinFormsWorld.App.GetMainPage().ConvertPageToUIElement(this);
}
}
Views and Layouts
Xamarin.Forms 使用简单的 API 通过 Control 和 Layout 对象组合创建 UI。在运行时,Xamarin.Forms control 映射到相应原生控件呈现。主要类包括:
- View:相当于标签、按钮、文本框等 UI 元素。
- Page:代表应用程序的一个屏幕,类似于 Android 的 Activity、Windows Phone 的 Page 或 iOS 的 View Controller。
- Layout:View 的子类,作为放置其他 View 和 Layout 的容器,包含组织子 view 的逻辑。
- Cell:用于代表列表或表格中的项目,描述每个元素的展示方式。
常用 Control 包括 Label、Entry、Button、Image 和 ListView。Layout 控制控件布局,分为两类:
- Managed Layouts:Layout 负责内部控件的位置和大小,遵循 CSS box 模型,应用程序不直接设置控件大小和位置。例如
StackLayout。 - Unmanaged Layouts:Layout 不自动调整控件位置,用户需指定位置和大小。例如
AbsoluteLayout。
StackLayout
StackLayout 是最常见的 managed layout,可满足跨平台应用程序中无视屏幕尺寸自动调整控件位置的需求。子元素按加入顺序定位,横向或竖向排列。StackLayout 使用空间取决于 HorizontalOptions 和 VerticalOptions 属性设置,默认使用整个屏幕区域。例如,使用 StackLayout 排列三个 Label 控件:
public class StackLayoutExample: ContentPage
{
public StackLayoutExample()
{
Padding = new Thickness(20);
var red = new Label { Text = "Stop", BackgroundColor = Color.Red, Font = Font.SystemFontOfSize(20) };
var yellow = new Label { Text = "Slow down", BackgroundColor = Color.Yellow, Font = Font.SystemFontOfSize(20) };
var green = new Label { Text = "Go", BackgroundColor = Color.Green, Font = Font.SystemFontOfSize(20) };
Content = new StackLayout { Spacing = 10, Children = { red, yellow, green } };
}
}
使用 XAML 实现相同布局:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="HelloXamarinFormsWorldXaml.StackLayoutExample1" Padding="20">
<StackLayout Spacing="10">
<Label Text="Stop" BackgroundColor="Red" Font="20" />
<Label Text="Slow down" BackgroundColor="Yellow" Font="20" />
<Label Text="Go" BackgroundColor="Green" Font="20" />
</StackLayout>
</ContentPage>
可通过设置 Orientation 和 VerticalOptions 属性调整排列方向。例如,水平排列:
Content = new StackLayout
{
Spacing = 10,
VerticalOptions = LayoutOptions.End,
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.Start,
Children = { red, yellow, green }
};
XAML 版本类似。尽管不能显式设置 StackLayout 内控件的大小,但可通过 HeightRequest 和 WidthRequest 属性指定。
Absolute Layout
AbsoluteLayout 属于 unmanaged layout,控件必须显式设置位置。这类似于传统 Windows Forms 或 iOS 平台(无 constraints)的控件定位。由于设置精确位置,需在不同尺寸屏幕上测试效果。简单例子:
public class MyAbsoluteLayoutPage : ContentPage
{
public MyAbsoluteLayoutPage()
{
var red = new Label { Text = "Stop", BackgroundColor = Color.Red, Font = Font.SystemFontOfSize(20), WidthRequest = 200, HeightRequest = 30 };
var yellow = new Label { Text = "Slow down", BackgroundColor = Color.Yellow, Font = Font.SystemFontOfSize(20), WidthRequest = 160, HeightRequest = 160 };
var green = new Label { Text = "Go", BackgroundColor = Color.Green, Font = Font.SystemFontOfSize(20), WidthRequest = 50, HeightRequest = 50 };
var absLayout = new AbsoluteLayout();
absLayout.Children.Add(red, new Point(20,20));
absLayout.Children.Add(yellow, new Point(40,60));
absLayout.Children.Add(green, new Point(80,180));
Content = absLayout;
}
}
XAML 版本:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="HelloXamarinFormsWorldXaml.AbsoluteLayoutExample" Padding="20">
<AbsoluteLayout>
<Label Text="Stop" BackgroundColor="Red" Font="20" AbsoluteLayout.LayoutBounds="20,20,200,30" />
<Label Text="Slow down" BackgroundColor="Yellow" Font="20" AbsoluteLayout.LayoutBounds="40,60,160,160" />
<Label Text="Go" BackgroundColor="Green" Font="20" AbsoluteLayout.LayoutBounds="80,180,50,50" />
</AbsoluteLayout>
</ContentPage>
注意控件加入 Children 集合的顺序影响 Z-order:第一个加入的在最底层,后续加入的覆盖其上,可能隐藏其他控件。
Xamarin.Forms 中的列表
ListView 是移动应用程序中常见控件,用于展示集合中的项目。每个项目由一个 cell 组成,默认使用 TextCell 输出单行文本。简单例子:
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = new string [] { "Buy pears", "Buy oranges", "Buy mangos", "Buy apples", "Buy bananas" };
Content = new StackLayout { VerticalOptions = LayoutOptions.FillAndExpand, Children = { listView } };
和一个自定义类绑定
自定义类也可绑定到 ListView。例如,定义 TodoItem 类:
public class TodoItem {
public string Name { get; set; }
public bool Done { get; set; }
}
使用:
listView.ItemsSource = new TodoItem [] {
new TodoItem {Name = "Buy pears"},
new TodoItem {Name = "Buy oranges", Done=true},
new TodoItem {Name = "Buy mangos"},
new TodoItem {Name = "Buy apples", Done=true},
new TodoItem {Name = "Buy bananas", Done=true}
};
为控制显示内容,创建绑定并设置属性,例如绑定 Name 属性:
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Name");
在 ListView 中选择一个项目
通过 ItemSelected 事件响应点击,例如显示提示框:
listView.ItemSelected += (sender, e) => {
DisplayAlert("Tapped!", e.Item + " was tapped.", "OK", null);
};
在 NavigationPage 中,可使用 Navigation.PushAsync 导航到新 Page。事件中通过 e.SelectedItem 访问选中对象,绑定到新 Page 并展示:
listView.ItemSelected += (sender, e) => {
var todoItem = (TodoItem)e.SelectedItem;
var todoPage = new TodoItemPage(todoItem);
Navigation.PushAsync(todoPage);
};
每个平台的导航方式不同。
自定义一个 Cell 的样式
通过创建 ViewCell 的子类并设置到 ListView 的 ItemTemplate 属性来自定义 Cell 样式。例如,创建一个包含 Image 和两个 Label 的 Cell:
class EmployeeCell : ViewCell
{
public EmployeeCell()
{
var image = new Image { HorizontalOptions = LayoutOptions.Start };
image.SetBinding(Image.SourceProperty, new Binding("ImageUri"));
image.WidthRequest = image.HeightRequest = 40;
var nameLayout = CreateNameLayout();
var viewLayout = new StackLayout() { Orientation = StackOrientation.Horizontal, Children = { image, nameLayout } };
View = viewLayout;
}
static StackLayout CreateNameLayout()
{
var nameLabel = new Label { HorizontalOptions= LayoutOptions.FillAndExpand };
nameLabel.SetBinding(Label.TextProperty, "DisplayName");
var twitterLabel = new Label { HorizontalOptions = LayoutOptions.FillAndExpand, Font = Fonts.Twitter };
twitterLabel.SetBinding(Label.TextProperty, "Twitter");
var nameLayout = new StackLayout() { HorizontalOptions = LayoutOptions.StartAndExpand, Orientation = StackOrientation.Vertical, Children = { nameLabel, twitterLabel } };
return nameLayout;
}
}
使用自定义 Cell:
List<Employee> myListOfEmployeeObjects = GetAListOfAllEmployees();
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = myListOfEmployeeObjects;
listView.ItemTemplate = new DataTemplate(typeof(EmployeeCell));
ListView 将 Employee 对象通过 BindingContext 传入 EmployeeCell。
使用 XAML 来自定义一个列表
上述代码可用 XAML 实现:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:XamarinFormsXamlSample;assembly=XamarinFormsXamlSample" xmlns:constants="clr-namespace:XamarinFormsSample;assembly=XamarinFormsXamlSample" x:Class="XamarinFormsXamlSample.Views.EmployeeListPage" Title="Employee List">
<ListView x:Name="listView" IsVisible="false" ItemsSource="{x:Static local:App.Employees}" ItemSelected="EmployeeListOnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding ImageUri}" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="{Binding DisplayName}" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding Twitter}" Font="{x:Static constants:Fonts.Twitter}"/>
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
数据绑定
Xamarin.Forms 应用程序使用数据绑定方便地显示和交互数据,在用户界面和底层应用程序间建立连接。例如,当用户编辑文本框时,绑定自动更新关联对象的属性。BindableObject 包含实现数据绑定的内容。
数据绑定定义两个对象间的关系:source 对象提供数据,target 对象消费数据(通常用于显示)。例如,Label 显示 Employee 对象,Employee 是 source,Label 是 target。
在 Xamarin.Forms 对象中使用数据绑定的步骤:
- 设置绑定对象的
BindingContext属性,source 对象一般实现INotifyPropertyChanged接口。 - Xamarin.Forms 对象调用
SetBinding方法进行绑定。
SetBinding 方法有两个参数:第一个是绑定类型信息,第二个提供绑定内容和方式,通常设置为 BindingContext 的属性名。如果直接绑定,可写为:
someLabel.SetBinding(Label.TextProperty, new Binding("."));
点号告诉 Xamarin.Forms 直接使用 BindingContext 作为数据源,而不是其属性,适用于 BindingContext 是简单类型如 string 或 integer。
例如,绑定一个 Employee 对象到 Page,Page 使用 Image、Label、Entry 和 Button 控件。Page 对象通过构造函数传入 Employee 对象:
public EmployeeDetailPage(Employee employeeToDisplay)
{
this.BindingContext = employeeToDisplay;
var firstName = new Entry() { HorizontalOptions = LayoutOptions.FillAndExpand };
firstName.SetBinding(Entry.TextProperty, "FirstName");
// 其余代码省略...
}
第一行设置 BindingContext 为 .NET 对象,告诉界面绑定源;下一行实例化 Entry 控件;最后一行绑定 Entry 的 Text 属性到 BindingContext 对象的 FirstName 属性。Entry 控件的变化自动更新 employeeToDisplay 对象,反之亦然,绑定是双向的。
INotifyPropertyChanged
为使双向绑定工作,模型类必须实现 INotifyPropertyChanged 接口。该接口用于对象值变化时通知客户端:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
实现 INotifyPropertyChanged 的对象在属性值变化时必须触发 PropertyChanged 事件。简单例子:
public class MyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (value.Equals(_firstName, StringComparison.Ordinal)) return;
_firstName = value;
OnPropertyChanged();
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
当 FirstName 变化时,OnPropertyChanged 被调用触发事件。注意 propertyName 参数使用 CallerMemberName 特性,当参数为空时自动填充方法名。
Navigation
Navigation 可视为后进先出的 Page 栈。在应用程序中跳转到新 Page 时压入栈,返回时弹出当前 Page。Xamarin.Forms 导航使用 INavigation 接口:
public interface INavigation
{
Task PushAsync(Page page);
Task<Page> PopAsync();
Task PopToRootAsync();
Task PushModalAsync(Page page);
Task<Page> PopModalAsync();
}
这些方法返回 Task 对象确保操作成功。
Xamarin.Forms 内置 NavigationPage 对象实现接口管理 Page,在屏幕上方添加导航工具栏,显示标题和平台样式的返回按钮。例如,在第一个 Page 中使用 NavigationPage:
public static Page GetMainPage()
{
var mainNav = new NavigationPage(new EmployeeListPage());
return mainNav;
}
在当前 Page 中显示 LoginPage 调用:
await Navigation.PushAsync(new LoginPage());
新的 LoginPage 对象被压入 Navigation 栈。退回前一 Page,LoginPage 调用:
await Navigation.PopAsync();
模态导航类似:
await Navigation.PushModalAsync(new LoginPage());
返回调用页:
await Navigation.PopModalAsync();