Xamarin.Forms 简介

An Introduction to Xamarin.Forms

来源:http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-xamarin-forms/

概览

Xamarin.Forms 是一个帮助开发者快速创建跨平台UI的框架。它为ios,Android,Windows Phone上的原生控件的使用提供了一层抽象.这意味着应用程序之间可以共享大部分UI代码,同时还能保持相应平台的界面外观样式。

Xamarin.Forms使用C#编写,能够适应于快速开发越来越复杂的应用程序。因为采用 Xamarin.Form的应用程序是原生应用程序,不必受制于其他类似开发工具包的限制,如浏览器沙箱, 有限的API,糟糕的性能. 使用 Xamarin.Forms编写的程序能够使用下层平台的任何API或者特性,包括但不限于CoreMotion, PassKit, and StoreKit 之于iOS; NFC 和Google Play Services 之于 Android;  Tiles 之于 Windows Phone.这也意味着你可以开发一个应用程序部分使用 Xamarin.Forms创建的UI,部分使用原生 UI 开发包来编写.

Xamarin.Forms 应用程序和传统的跨平台应用的架构是一样的。. 最常见的办法是使用 Portable Libraries 或者 Shared Projects 来共享代码, 然后创建平台相关的项目来消费使用共享代码.

Xamarin.Forms使用两种方式创建用户界面。第一种是用Xamarin Forms提供的API直接在源代码里创建UI,另一种可选的方法是使用 Extensible Application Markup Language (XAML),(一种MS用于定义界面的标记语言). 用户界面将使用XAML定义在一个XML文件中,运行时的行为则被定义在单独的代码文件中。了解更多关于XAML,可以阅读 XAML Overview 文档 What is 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 系统要求

在OSX上开发Xamarin.Forms程序需要安装Xamarin Studio 5 . 开发iOS程序则要求安装 Xcode 5  (操作系统要求 OS X 10.8 或以上). Windows Phone 程序没法在OSX上开发; IDE也没法创建包含Window Phone的项目模板.

Windows 系统要求

Xamarin.Forms 应用程序可以使用Visual Studio 2012(或更新的版本)开发.

  • PCL 解决方案模板需要.NET 4.5上的Profile 78 ( Visual Studio 2012 可以安装Net4.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 新建 Xamarin.Forms 解决方案的话则支持 iOS, Android和 Windows Phone. 需要注意的是虽然 Xamarin Studio不支持创建Windows Phone 应用程序,但是他还是能够打开一个包含Windows Phone 应用程序项目的解决方案 (如一个被Visual Studio创建的解决方案). 你可以查看 Windows Phone 代码,但是没法编译和部署在Xamarin Studio.

查看一个 Xamarin.Forms 应用程序

默认的模板创建一个最简单 Xamarin.Forms 应用程序. 如果你运行它,你会看到如下界面:

在上面的截图中,每个界面都是使用Xamarin Forms的Page 的效果. 一个Xamarin.Forms.Page在Android中 就是Activity对象 ,在iOS中就是 View Controller对象, 在Windows Phone就是 Page 对象. 上面截图的HelloXamarinFormsWorld 例子是使用一个Xamarin.Forms.ContentPage 对象,然后展示一个标签.

为了最大化重用启动代码, Xamarin.Forms 应用程序会创建一个叫App的简单类,作用是实例化第一个展示的Page对象.下面的代码就是一个App类的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象, 每个平台上的项目必须初始化 Xamarin.Forms 框架并在启动时提供一个实例化的ContentPage的对象. 不同平台上的启动代码是不一样的。

Android

在Android中启动初始化 Xamarin.Forms 页面, 你必须创建一个使用MainLauncher特性的Activity,就像之前的传统的Android应用一样; 除此以外你的activity 还必须继承至Xamarin.Forms.Platform.Android.AndroidActivity,然后重写OnCreate方法,在其中进行 Xamarin.Forms framework的初始化,显示初始的Page等工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
        }
    }
}

上述代码会创建一个 Xamarin.Android 应用程序

iOS

在一个 Xamarin.iOS 应用程序中,需要在 AppDelegate 类中初始化 Xamarin.Forms 框架,然后设置RootViewController为初始的Xamarin.Forms Page. 如下面代码显示的,这些都要在 FinishedLaunching 方法中完成 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[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;
    }
}

和andoroid一样,  FinishedLaunching事件的第一步是通过Xamarin.Forms.Forms.Init()初始化 Xamarin.Forms 框架。这样做原因是因为Xamarin.Forms需要在应用程序中全局加载. 下一步是设置应用程序的根view controller对象. 这需要调用在跨平台类库中创建的HelloWordPage对象的 CreateViewController()方法。

Windows Phone

在一个 Windows Phone项目中初始页会初始化Xamarin.Forms框架,然后用 Xamarin.Forms Page对象设置起始页内容. 下面代码是如何操作的一个例子:

1
2
3
4
5
6
7
8
9
10
public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        Forms.Init();
        Content = HelloXamarinFormsWorld.App.GetMainPage().ConvertPageToUIElement(this);
    }
}

既然我们已经开始对 Xamarin.Forms有些了解,然后我们开始更详细讨论Xamarin Forms.

Views and Layouts

Xamarin.Forms使用了一个很简单的API,通过Control对象和Layouts对象组合用于创建UI界面。 在运行时, Xamarin.Forms control会映射到相应的原生控件,并用原生控件来呈现.用于 Xamarin.Forms 应用的界面展示主要是如下4个类:

  • View - 在其他平台通常会被称为controls 或 widgets. 就是相当于标签,按钮,文本框等等UI元素.
  • Page - 一个Page在应用程序程序代表一个屏幕的内容. 他类似于Android的 Activity, Windows Phone 的Page,  iOS 的View Controller.
  • Layout - 一种View的子类. 作为放置界面显示的View对象和其他Layout的容器. Layout 通常包含如何组织呈现子view的逻辑.
  • Cell - 这个类是专门的元素用于代表list或table中的项目,它描述了列表中每个元素是如何展示的.

下面是常用的Control:

Xamarin.Forms Control Description
Label 只读显示文本
Entry 单行输入文本框
Button 按钮
Image 用于展示图片的控件
ListView 列表清单. 

Controls 本身是由 layout进行布局控制的. Xamarin.Forms 有两类不同的布局控制方式:

  • Managed Layouts -  layout负责内部的控件的位置和大小,并且遵循 CSS box model. 应用程序不会直接尝试去设置控件的大小和位置. 内置的一个Managed Layout的是 theStackLayout.
  • Unmanaged Layouts -和managed layouts相反, unmanaged layouts 不会自动调整屏幕中控件的位置. 通常用户在加入到layout时要自己指定好位置和大小。内置的 AbsoluteLayout 是一个 unmanaged layout .

让我们具体讨论下 StackLayout 和AbsoluteLayout .

StackLayout

StackLayout 是最常见的managed layout. StackLayout 可以满足那些跨平台应用程序开发中希望无视屏幕尺寸,系统自动调整控件位置的需求。每个子元素按照加入layout的顺序依次被定位,无论横向还是竖向 . StackLayout会使用多少空间,取决于HorizontalOptions 和LayoutOptions 属性的设置, 默认StackLayout 会使用整个屏幕区域

下列代码是使用 StackLayout 排列3个Label 控件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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进行同样的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>

下面的截图我们可以看到默认 StackLayout 会垂直排列

可以通过下面的代码对 Orientation 和Vertical属性进行设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StackLayoutExample: ContentPage
{
    public StackLayoutExample()
    {
        // 为看得更清楚创建label的代码已被移除

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

下列代码是使用XAML进行同样的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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.StackLayoutExample2"
             Padding="20">

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

修改后代码的运行截图如下

尽管不能显式地设置StackLayout内控件的大小, 但是可以通过HeightRequest 和WidthRequest 属性告诉layout引擎控件具体的大小. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var red = new Label
{
    Text = "Stop",
    BackgroundColor = Color.Red,
    Font = Font.SystemFontOfSize (20),
    WidthRequest = 100
};
var yellow = new Label
{
    Text = "Slow down",
    BackgroundColor = Color.Yellow,
    Font = Font.SystemFontOfSize (20),
    WidthRequest = 100
};
var green = new Label
{
    Text = "Go",
    BackgroundColor = Color.Green,
    Font = Font.SystemFontOfSize (20),
    WidthRequest = 200
};

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

下列代码是使用XAML进行同样的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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.StackLayoutExample3"
             Padding="20">

  <StackLayout Spacing="10"
               VerticalOptions="End"
               Orientation="Horizontal"
               HorizontalOptions="Start">

    <Label Text="Stop"
           BackgroundColor="Red"
           Font="20"
           WidthRequest="100" />

    <Label Text="Slow down"
           BackgroundColor="Yellow"
           Font="20"
           WidthRequest="100" />

    <Label Text="Go"
           BackgroundColor="Green"
           Font="20"
           WidthRequest="200" />

  </StackLayout>
</ContentPage>

修改后的程序运行结果:

Absolute Layout

AbsoluteLayout属于 unmanaged layout. 在layout内的控件必须显式设置好自己的位置. 这个概念和老早的windows forms和ios平台(没有使用constarints)上控件定位的方法很像. 由于设置了具体精确的定位位置,我们需要在不同尺寸的屏幕上对layout进行测试来保证效果。

下面是使用 AbsoluteLayout的简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
    }
}

Conceptually 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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中的列表

ListViews在移动应用程序是一个常见控件,所以我们可以详细讨论下. ListView 用于展示一个集合中的项目。在ListVIew中每个项目都是由一个cell组成.默认ListView会使用内置的 TextCell 来输出一个单行文本。下面的代码是使用ListView的简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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来绑定显示. 我们定义以下类:

1
2
3
4
 public class TodoItem {
    public string Name { get; set; }
    public bool Done { get; set; }
}

 ListView 可以像下面这样使用:

1
2
3
4
5
6
7
listView.ItemSource = 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}
};

为了控制在列表中显示的内容,需要创建一个binging,并设置绑定的属性 - 在这个例子中是 Name 属性.

1
2
listview.ItemTemplate = new DataTemplate(typeof(TextCell));
listview.ItemTemplate.SetBinding(TextCell.TextProperty, "Name");

 

在ListView中选择一个项目

在ListView中实现ItemSelected 事件可以响应界面上的点击事件,下面代码展示显示一个简单提示框:

1
2
3
listView.ItemSelected += (sender, e) => {
    DisplayAlert("Tapped!", e.Item + " was tapped.", "OK", null);
};

在一个 NavigationPage中的话,可以使用 Navigation.PushAsync 方法打开一个新的Page进行导航. ItemSelected 事件中可以通过 e.SelectedItem访问选中的绑定对象, 我们可以继续把对象绑定到新的Page然后通过 PushAsync方法展示:

1
2
3
4
5
listView.ItemSelected += (sender, e) => {
    var todoItem = (TodoItem)e.SelectedItem;
    var todoPage = new TodoItemPage(todoItem); // so the new page shows correct data
    Navigation.PushAsync(todoPage);
};

每个平台的导航方式不一样的.更多内容在 Navigation.

自定义一个Cell的样式

我们可以通过创建一个ViewCell的子类,并设置到ListView的ItemTemplate属性上来实现Cell样式的自定义

看一下下面的截图样式

这个cell由一个 Image 控件和两个Label控件组成. 为了创建这个自定义的布局,我们需要创建一个 ViewCell的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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;
    }
}

代码中的内容很多

  • 它加入一个 Image控件,然后绑定ImageUri属性到 Employee 对象. 
  • 它创建了 StackLayout包含垂直排列的两个标签. 标签绑定Employee 对象的DisplayName 属性和 Twitter 属性.
  • 它创建另一个 StackLayout 用于放置Image 和第二步创建的 StackLayout . 然后使用水平排列.

一旦自定义的 cell类创建好,我们可以使用在ListView中 DataTemplate对象:

1
2
3
4
5
6
7
List<Employee> myListOfEmployeeObjects = GetAListOfAllEmployees();
var listView = new ListView
{
    RowHeight = 40
};
listView.ItemsSource = myListOfEmployeeObjects;
listView.ItemTemplate = new DataTemplate(typeof(EmployeeCell));

向 ListView传入一个Empolyee集合对象,每个cell都会使用EmployeeCell类来呈现. ListView 会把 Employee 对象通过BindingContext传入EmployeeCell 

使用XAML来自定义一个列表

之前的代码可以通过如下XAML实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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对象消费使用来自source数据 (通常是显示). 例如,一个Label会显示Employee 对象.Employee 对象就是 source, Label 就是 target.

如何在 Xamarin.Forms 对象(如 Page 和 Control) 中使用数据绑定,需要遵循下面两步:

  • 设置要绑定对象的 BindingContext 属性 ,用于绑定的源对象一般都要实现 INotifyPropertyChanged 接口.
  •  Xamarin.Forms 的对象要调用SetBinding 方法进行绑定.

 SetBinding 方法有两个参数. 第一个参数是绑定的类型信息. 第二个参数需要提供绑定什么内容和如何绑定的信息. 一般情况下可以设置为BindingContext中文本型的属性名, 如果我们直接要绑定, 我们可以这样写

1
someLabel.SetBinding(Label.TextProperty, new Binding("."));

点号告诉Xamarin.Forms直接使用 BindingContext作为数据源,而不是 BindingContext的某个属性. 一般在BindingContext是一个简单类型,如string或integer.

为了帮助理解如何绑定一个对象到Page上,可以看看下面的截图

这个 Page使用了以下控件:

  • Xamarin.Forms.Image
  • Xamarin.Forms.Label
  • Xamarin.Forms.Entry
  • Xamarin.Forms.Button

Page对象通过构造子传入Employee对象. 

1
2
3
4
5
6
7
8
9
10
11
12
public EmployeeDetailPage(Employee employeeToDisplay)
{
    this.BindingContext = employeeToDisplay;

    var firstName = new Entry()
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand
                };
    firstName.SetBinding(Entry.TextProperty, "FirstName");

    // Rest of the code omitted…
}

代码第一行设置了 BindingContext为 一个.NET 对象 –这告诉底层界面需要绑定对象是谁. 下一行实例化了 Xamarin.Forms.Entry 控件. 最后一行绑定 Xamarin.Forms.Entry 和用于显示的employee对象;  Entry控件Text 属性被绑定到BindingContext对象的 FirstName 属性. Entry控件的变化会自动更新到employeeToDisplay 对象. 同样, 如果 employeeToDisplay.FirstName的变化也会引起 Entry 控件的显示.绑定是双向的.

为了让双向绑定起作用, 模型类必须实现 INotifyPropertyChanged接口.

INotifyPropertyChanged

INotifyPropertyChanged 接口用于当一个对象值发生变化时需要通过客户端的场景,接口很简单:

1
2
3
4
5
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

INotifyPropertyChanged的对象必须在他的属性值变化时触发 PropertyChanged 事件. 下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (value.Equals(_firstName, StringComparison.Ordinal))
            {
                // Nothing to do - the value hasn't changed;
                return;
            }
            _firstName = value;
            OnPropertyChanged();

        }
    }

    void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

当某个 MyObject实例的FirstName 发生变化, OnPropertyChanged会被调用来触发PropertyChanged 事件.

注意参数propertyName 使用了 CallMemberName 特性. 当OnPropertyChanged调用时参数为空,  CallMemberName 特性会调用OnPropertyChanged方法的方法名.

Navigation

现在我们已经明白如何创建一个page并显示控件,接下来我们讨论下如何在page间导航跳转。. Navigation可以被看做一个后进先出,里面全是Page对象的栈. 在应用程序中从一个page跳到另一个page,只要在栈中压入一个新的page对象;返回到之前的page则只要在栈中弹出当前的Page。Xamarin.Forms 中的导航使用 INavigation 接口

1
2
3
4
5
6
7
8
public interface INavigation
{
    Task PushAsync(Page page);
    Task<Page> PopAsync();
    Task PopToRootAsync();
    Task PushModalAsync(Page page);
    Task<Page> PopModalAsync();
}

这些方法都返回 Task对象,用于调用代码确保push和pop page是否成功

Xamarin.Forms 内置了 NavigationPage 对象,实现了上面的接口用于管理page.  NavigationPage 类会屏幕上方在加入一个导航工具栏,上面会显示一个标题,还有与平台相应样式的返回按钮用于退回到前一个page。 下面的代码展示如何在第一个page中使用 NavigationPage:

1
2
3
4
5
public static Page GetMainPage()
{
    var mainNav = new NavigationPage(new EmployeeListPage());
    return mainNav;
}

为了在当前page中显示LoginPage则需要调用 INavigation.PushAsync方法

1
await Navigation.PushAsync(new LoginPage());

当新的LoginPage 对象被压入 Navigation 的栈中. 退回前一page, LoginPage 必须调用:

1
await Navigation.PopAsync();

模态的导航也是类似的. :

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

返回到调用页的话, LoginPage必须使用:

1
await Navigation.PopModalAsync();

 欢迎加入群88684603 一起讨论学习

posted @ 2014-08-01 13:50  kwok.io  阅读(2930)  评论(2编辑  收藏  举报