Xamarin.Forms入门:创建跨平台用户界面

Viewed 0

Xamarin.Forms 是一个跨平台的、基于原生控件的UI工具包,开发人员可以轻松创建适用于 Android、iOS 以及 Windows Phone 的用户界面。通过使用平台的原生控件来渲染用户界面,基于 Xamarin.Forms 的应用程序在外观上与平台完全一致。本文旨在快速介绍如何使用 Xamarin.Forms 进行应用程序开发。

简介

Xamarin.Forms 帮助开发人员快速构建跨平台UI,实现一次编码、多平台部署。如果你需要为多个平台开发应用,Xamarin.Forms 能有效避免重复的界面逻辑工作。它允许使用 C# 语言构建UI,并且由于应用程序完全是原生的,不受浏览器沙盒或底层API限制,可以直接调用操作系统提供的API,如 iOS 的 CoreMotion、PassKit、StoreKit,安卓的 NFC 和 Google Play Services。这意味着你可以用 Xamarin.Forms 构建UI,而其他部分使用原生语言。

在架构上,基于 Xamarin.Forms 的应用通常采用共享逻辑层的方案,通过 Portable Libraries 或 Shared Projects 共享代码,平台相关部分可复用这些代码。开发人员可以通过 C# 代码或 XAML 来构建UI,运行时行为写在对应的文件中。

本文涵盖 Xamarin.Forms 框架的核心概念,包括安装、项目建立、控件使用、页面导航和数据绑定。

系统需求

  • iOS:由于 Apple 限制,需要一台 Mac 作为 Build Host。
    • Windows 7 或更高版本
    • Visual Studio 2010/2012
    • OS X Lion 或更高版本
    • Xcode IDE 及 iOS SDK
  • Android:可完全在 Windows 上开发。
    • Windows 7 或更高版本
    • Java SDK
    • Android SDK
    • Xamarin.Android for Visual Studio

使用Xamarin Forms开始编程

开发人员可以在 Xamarin Studio 或 Visual Studio 中创建 Xamarin.Forms 项目,有四种项目类型可选:Portable Library(用于代码共享的类库)、Xamarin.Android Application、Xamarin.iOS Application 和 Windows Phone Application。

在 Xamarin Studio 中,选择 File > New > Solution,然后在 New Solution 对话框中点击 C# > Mobile Apps,选择 Blank App (Xamarin.Forms Portable),输入项目名称如 “HelloXamarinFormsWorld”,点击 OK 创建新工程。

Xamarin.Forms 应用程序

运行上述程序后,会显示一个简单界面。在 Xamarin.Forms 中,每个屏幕对应一个 Page 概念,Xamarin.Forms.Page 在安卓中与 Activity 对应,在 iOS 中与 ViewController 对应,在 Windows Phone 中与 Page 对应。示例工程使用 ContentPage,并添加了一个 Label 控件。App 类负责初始化应用程序首页,例如:

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

Android

创建一个 Activity 类型,并用 MainLauncher 特性修饰。在 OnCreate 方法中,初始化 Xamarin.Forms 框架并设置初始界面:

namespace HelloXamarinFormsWorld.Android
{
    [Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : AndroidActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            Xamarin.Forms.Forms.Init(this, bundle);
            SetPage(App.GetMainPage());
        }
    }
}

运行后,应用程序将显示一个带有 Label 的页面。

iOS

对于 Xamarin.iOS 应用程序,在 AppDelegate 的 FinishedLaunching 方法中初始化框架并设置 RootViewController:

[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

类似地,在 MainPage 中初始化:

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
        Forms.Init();
        Content = App.GetMainPage().ConvertPageToUIElement(this);
    }
}

视图与布局

Xamarin.Forms 使用控件进行布局,每个控件在运行时对应一个原生控件。常用类型包括:

  • View:如 Label、Button、输入框等。
  • Page:单个屏幕,对应安卓的 Activity、iOS 的 ViewController、Windows Phone 的 Page。
  • Layout:布局或容器控件。
  • Cell:表格或列表控件的子项目。

常用控件有 Label(文本展示)、Entry(单行文本输入框)、Button(按钮)、Image(图片)和 ListView(列表控件)。

布局容器有两种类型:

  • Managed Layout:类似 CSS 盒模型,通过设置子控件位置和大小布局,如 StackLayout。
  • Unmanaged Layouts:需要直接设置子控件位置和大小,如 AbsoluteLayout。

堆栈式布局

堆栈式布局简化了跨平台UI搭建,子元素按添加顺序摆放,方向可为竖直或水平。以下示例创建三个 Label 控件并添加到 StackLayout:

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:///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 属性:

Content = new StackLayout { Spacing = 10, VerticalOptions = LayoutOptions.End, Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.Start, Children = { red, yellow, green } };

XAML 版本:

<StackLayout Spacing="10" VerticalOptions="End" Orientation="Horizontal" HorizontalOptions="Start">
    <Label Text="Stop" BackgroundColor="Red" Font="20" />
    <Label Text="Slow down" BackgroundColor="Yellow" Font="20" />
    <Label Text="Go" BackgroundColor="Green" Font="20" />
</StackLayout>

可以通过 HeightRequest 和 WidthRequest 指定子元素大小,例如设置 Label 的宽度。

绝对布局

绝对布局需要指定每个子元素的位置。示例:

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:///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>

子元素添加顺序影响 Z-Order,先添加的元素可能被后添加的元素遮挡。

列表

ListView 用于展现数据集合,每个条目包含在单元格内。默认使用 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 } };

可以绑定自定义数据类型,例如 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 } };
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Name");

选择条目

通过 ItemSelected 事件处理条目选择:

listView.ItemSelected += async (sender, e) => { await DisplayAlert("Tapped!", e.SelectedItem + " was tapped.", "OK"); };

也可以进行页面导航:

listView.ItemSelected += async (sender, e) => {
    var todoItem = (TodoItem)e.SelectedItem;
    var todoPage = new TodoItemPage(todoItem);
    await Navigation.PushAsync(todoPage);
};

自定义单元格样式

可以创建复杂单元格,例如包含 Image 和多个 Label。示例代码定义 EmployeeCell 类:

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");
        return new StackLayout() { HorizontalOptions = LayoutOptions.StartAndExpand, Orientation = StackOrientation.Vertical, Children = { nameLabel, twitterLabel } };
    }
}

然后绑定数据:

List<Employee> myListOfEmployeeObjects = GetAListOfAllEmployees();
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = myListOfEmployeeObjects;
listView.ItemTemplate = new DataTemplate(typeof(EmployeeCell));

XAML 版本:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http:///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>

数据绑定

数据绑定允许控件展示和同步数据。示例页面包含 Image、Label、Entry 和 Button 控件。在构造函数中绑定业务数据:

public EmployeeDetailPage(Employee employeeToDisplay)
{
    this.BindingContext = employeeToDisplay;
    var firstName = new Entry() { HorizontalOptions = LayoutOptions.FillAndExpand };
    firstName.SetBinding(Entry.TextProperty, "FirstName");
    // 其他代码省略
}

页面导航

页面导航基于后进先出的堆栈结构。Xamarin.Forms 定义了 INavigation 接口,NavigationPage 类型实现该接口并提供导航条。示例初始化导航:

public static Page GetMainPage() { var mainNav = new NavigationPage(new EmployeeListPage()); return mainNav; }

推入新页面:

await Navigation.PushAsync(new LoginPage());

返回前一个页面:

await Navigation.PopAsync();

模态对话框类似:

await Navigation.PushModalAsync(new LoginPage());
await Navigation.PopModalAsync();

小结

本文介绍了 Xamarin.Forms 的基本概念和使用方法,包括安装、项目创建、UI构建、数据绑定和页面导航。Xamarin.Forms 是一个强大的跨平台UI工具包,能帮助开发人员高效构建原生移动应用。

0 Answers