一个好老好老的老程序员了。
全部博文(915)
分类: Android平台
2017-10-27 22:27:35
跨平台用户界面入门
Xamarin.Forms是一个允许开发人员快速创建跨平台用户界面的框架。它为用户界面提供了自己的抽象,将使用iOS,Android,Windows或Windows Phone上的原生控件进行渲染。这意味着应用程序可以共享其大部分用户界面代码,并且仍然保留目标平台的本机外观。
Xamarin.Forms允许应用程序的快速原型开发,随着时间的推移,应用程序可以演变为复杂的应用程序。由于Xamarin.Forms应用程序是本机应用程序,因此它们没有其他工具包的限制,例如浏览器沙盒,有限的API或性能不佳。使用Xamarin.Forms编写的应用程序能够利用底层平台的任何API或功能,例如(但不限于)iOS上的CoreMotion,PassKit和StoreKit; NFC和Android上的Google Play服务;和Windows上的瓷砖。此外,还可以创建使用Xamarin.Forms创建用户界面部分的应用程序,而使用本机UI工具包创建其他部分。
Xamarin.Forms应用程序的架构与传统的跨平台应用程序相同。最常见的方法是使用便携式库或共享项目来容纳共享代码,并创建将消耗共享代码的平台特定应用程序。
在Xamarin.Forms中创建用户界面有两种技术。第一种技术是用C#源代码完全创建UI。第二种技术是使用可扩展应用程序标记语言(XAML),一种用于描述用户界面的声明性标记语言。有关XAML的更多信息,请参阅XAML基础知识。
本文讨论了Xamarin.Forms框架的基础知识,并涵盖以下主题:
在Visual Studio for Mac和Visual Studio中,默认的Xamarin.Forms应用程序模板可以创建最简单的Xamarin.Forms解决方案,向用户显示文本。 如果您运行该应用程序,它应该类似于以下截图:
截图中的每个屏幕都对应于Xamarin.Forms中的一个页面。 页面表示Android中的活动,iOS中的View Controller或Windows通用平台(UWP)中的页面。 上述截图中的示例会实例化一个ContentPage对象,并使用它来显示一个Label。
为了最大限度地重用启动代码,Xamarin.Forms应用程序有一个名为App的类,负责实例化将显示的第一个页面。 以下代码可以看到App类的一个例子:
点击(此处)折叠或打开
- public class App : Application
- {
- public App ()
- {
- MainPage = new ContentPage {
- Content = new Label
- {
- Text = "Hello, Forms !",
- VerticalOptions = LayoutOptions.CenterAndExpand,
- HorizontalOptions = LayoutOptions.CenterAndExpand,
- }
- };
- }
- }
该代码实例化一个新的ContentPage对象,该对象将在页面上垂直和水平显示一个标签。
要在应用程序中使用此页面,每个平台应用程序必须初始化Xamarin.Forms框架,并在启动时提供ContentPage的实例。 该初始化步骤因平台而异,并在以下部分进行讨论。
要在iOS中启动最初的Xamarin.Forms页面,平台项目包括继承自Xamarin.Forms.Platform.iOS.FormsApplicationDelegate类的AppDelegate类,如以下代码示例所示:
点击(此处)折叠或打开
- [Register("AppDelegate")]
- public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
- {
- public override bool FinishedLaunching(UIApplication app, NSDictionary options)
- {
- global::Xamarin.Forms.Forms.Init ();
- LoadApplication (new App ());
- return base.FinishedLaunching (app, options);
- }
- }
FinishedLoading override通过调用Init方法来初始化Xamarin.Forms框架。 这将导致在通过调用LoadApplication方法设置根视图控制器之前,将Xamarin.Forms的iOS特定实现加载到应用程序中。
要在Android中启动Xamarin.Forms初始页面,平台项目包括使用MainLauncher属性创建活动的代码,并从FormsApplicationActivity类继承的活动,如下面的代码示例所示:
点击(此处)折叠或打开
- namespace HelloXamarinFormsWorld.Android
- {
- [Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true,
- ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
- public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
- {
- protected override void OnCreate(Bundle bundle)
- {
- base.OnCreate(bundle);
- Xamarin.Forms.Forms.Init(this, bundle);
- LoadApplication (new App ());
- }
- }
- }
OnCreate覆盖通过调用Init方法来初始化Xamarin.Forms框架。 这将导致在加载Xamarin.Forms应用程序之前,将Xamarin.Forms的Android特定实现加载到应用程序中。
在Windows运行时应用程序中,从App类调用初始化Xamarin.Forms框架的Init方法:
点击(此处)折叠或打开
- Xamarin.Forms.Forms.Init (e);
- if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
- {
- ...
- }
这将导致Xamarin.Forms的Windows Phone特定实现被加载到应用程序中。 最初的Xamarin.Forms页面由MainPage类启动,如下面的代码示例所示:
点击(此处)折叠或打开
- public partial class MainPage
- {
- public MainPage()
- {
- this.InitializeComponent();
- this.NavigationCacheMode = NavigationCacheMode.Required;
- this.LoadApplication(new HelloXamarinFormsWorld.App());
- }
- }
Xamarin.Forms应用程序加载了LoadApplication方法。
Xamarin.Forms也支持Windows 8.1。 有关如何配置此项目类型的信息,请参阅安装Windows项目。
在通用Windows平台(UWP)应用程序中,从App类调用初始化Xamarin.Forms框架的Init方法:
点击(此处)折叠或打开
- Xamarin.Forms.Forms.Init (e);
- if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
- {
- ...
- }
这导致将XAMarin.Forms的UWP特定实现加载到应用程序中。 最初的Xamarin.Forms页面由MainPage类启动,如下面的代码示例所示:
点击(此处)折叠或打开
- public partial class MainPage
- {
- public MainPage()
- {
- this.InitializeComponent();
- this.LoadApplication(new HelloXamarinFormsWorld.App());
- }
- }
Xamarin.Forms应用程序加载了LoadApplication方法。
用于创建Xamarin.Forms应用程序的用户界面的四个主要控制组。
在运行时,每个控件都将被映射到其本机的等价物,这将被渲染。
控件位于布局的内部。 现在将对StackLayout类实现常用布局进行检查。
StackLayout通过在屏幕上自动布置控件来简化跨平台应用程序开发,无论屏幕大小如何。 每个子元素按照添加的顺序水平或垂直放置。 StackLayout将使用多少空间取决于如何设置HorizontalOptions和VerticalOptions属性,但默认情况下StackLayout将尝试使用整个屏幕。
以下XAML代码显示了使用StackLayout排列三个Label控件的示例:
点击(此处)折叠或打开
等效的C#代码如下代码示例所示:
点击(此处)折叠或打开
- public class StackLayoutExample : ContentPage
- {
- public StackLayoutExample()
- {
- Padding = new Thickness(20);
- var red = new Label
- {
- Text = "Stop", BackgroundColor = Color.Red, FontSize = 20
- };
- var yellow = new Label
- {
- Text = "Slow down", BackgroundColor = Color.Yellow, FontSize = 20
- };
- var green = new Label
- {
- Text = "Go", BackgroundColor = Color.Green, FontSize = 20
- };
- Content = new StackLayout
- {
- Spacing = 10,
- Children = { red, yellow, green }
- };
- }
- }
默认情况下,StackLayout假定为垂直方向,如以下屏幕截图所示:
StackLayout的方向可以更改为水平方向,如以下XAML代码示例所示:
点击(此处)折叠或打开
等效的C#代码如下代码示例所示:
点击(此处)折叠或打开
- public class StackLayoutExample: ContentPage
- {
- public StackLayoutExample()
- {
- // Code that creates labels removed for clarity
- Content = new StackLayout
- {
- Spacing = 10,
- VerticalOptions = LayoutOptions.End,
- Orientation = StackOrientation.Horizontal,
- HorizontalOptions = LayoutOptions.Start,
- Children = { red, yellow, green }
- };
- }
- }
以下屏幕截图显示结果布局:
控件的大小可以通过HeightRequest和WidthRequest属性设置,如以下XAML代码示例所示:
点击(此处)折叠或打开
等效的C#代码如下代码示例所示:
点击(此处)折叠或打开
- var red = new Label
- {
- Text = "Stop", BackgroundColor = Color.Red, FontSize = 20, WidthRequest = 100
- };
- var yellow = new Label
- {
- Text = "Slow down", BackgroundColor = Color.Yellow, FontSize = 20, WidthRequest = 100
- };
- var green = new Label
- {
- Text = "Go", BackgroundColor = Color.Green, FontSize = 20, WidthRequest = 200
- };
- Content = new StackLayout
- {
- Spacing = 10,
- VerticalOptions = LayoutOptions.End,
- Orientation = StackOrientation.Horizontal,
- HorizontalOptions = LayoutOptions.Start,
- Children = { red, yellow, green }
- };
以下屏幕截图显示结果布局:
有关StackLayout类的更多信息,请参阅StackLayout。
ListView控件负责在屏幕上显示项目集合 - ListView中的每个项目都将包含在单个单元格中。 默认情况下,ListView将使用内置的TextCell模板并呈现单行文本。
以下代码示例显示了一个简单的ListView示例:
点击(此处)折叠或打开
- 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:
有关ListView控件的更多信息,请参阅ListView。
ListView控件还可以使用默认的TextCell模板显示自定义对象。
以下代码示例显示了TodoItem类:
点击(此处)折叠或打开
- public class TodoItem
- {
- public string Name { get; set; }
- public bool Done { get; set; }
- }
可以如下面的代码示例所示填充ListView控件:
点击(此处)折叠或打开
- 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显示的TodoItem属性,如下面的代码示例所示:
点击(此处)折叠或打开
- listView.ItemTemplate = new DataTemplate(typeof(TextCell));
- listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Name");
这将创建一个绑定,指定TodoItem.Name属性的路径,并将导致以前显示的屏幕截图。
有关绑定到自定义类的更多信息,请参阅ListView数据源。
要响应用户触摸ListView中的单元格,应该处理ItemSelected事件,如以下代码示例所示:
点击(此处)折叠或打开
- listView.ItemSelected += async (sender, e) => {
- await DisplayAlert("Tapped!", e.SelectedItem + " was tapped.", "OK");
- };
当包含在NavigationPage中时,PushAsync方法可用于打开具有内置后导航的新页面。 ItemSelected事件可以通过e.SelectedItem属性访问与单元格关联的对象,将其绑定到新页面,并使用PushAsync显示新页面,如以下代码示例所示:
点击(此处)折叠或打开
- listView.ItemSelected += async (sender, e) => {
- var todoItem = (TodoItem)e.SelectedItem;
- var todoPage = new TodoItemPage(todoItem); // so the new page shows correct data
- await Navigation.PushAsync(todoPage);
- };
每个平台都以自己的方式实现内置的后导航。 有关详细信息,请参阅导航。
有关ListView选择的更多信息,请参阅ListView交互性。
可以通过对ViewCell类进行子类化,并将此类的类型设置为ListView的ItemTemplate属性来定制单元格外观。
以下屏幕截图中显示的单元格由一个图像和两个标签控件组成:
要创建此自定义布局,ViewCell类应该被子类化,如下面的代码示例所示:
点击(此处)折叠或打开
- 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;
- }
- }
代码执行以下任务:
一旦创建了自定义单元格,就可以通过将ListView控件包装在DataTemplate中来消耗,如下面的代码示例所示:
点击(此处)折叠或打开
- List<Employee> myListOfEmployeeObjects = GetAListOfAllEmployees();
- var listView = new ListView
- {
- RowHeight = 40
- };
- listView.ItemsSource = myListOfEmployeeObjects;
- listView.ItemTemplate = new DataTemplate(typeof(EmployeeCell));
该代码将提供一个List List的员工列表。 每个单元格将使用EmployeeCell类渲染。 ListView将把Employee对象作为BindingContext传递给EmployeeCell。
有关定制单元格外观的更多信息,请参阅单元格外观。
上一节中ListView的XAML等同于以下代码示例:
点击(此处)折叠或打开
数据绑定连接两个对象,称为源和目标。 源对象提供数据。 目标对象将消耗(并经常显示)来自源对象的数据。 例如,Label(目标对象)通常将其Text属性绑定到源对象中的公共字符串属性。 下图说明了绑定关系:
数据绑定的主要好处是您不再需要担心在视图和数据源之间同步数据。 源对象中的更改将通过绑定框架自动推送到场景后的目标对象,并且可以将目标对象中的更改推送回源对象。
建立数据绑定是一个两步的过程:
有关数据绑定的更多信息,请参阅数据绑定基础。
以下代码显示了在XAML中执行数据绑定的示例:
点击(此处)折叠或打开
Entry.Text属性和Source对象的FirstName属性之间的绑定被建立。 在Entry控件中进行的更改将自动传播到employeeToDisplay对象。 同样,如果对employeeToDisplay.FirstName属性进行更改,Xamarin.Forms绑定引擎也将更新Entry控件的内容。 这被称为双向绑定。 为了双向绑定工作,模型类必须实现INotifyPropertyChanged接口。
虽然可以在XAML中设置EmployeeDetailPage类的BindingContext属性,但是它在代码中设置为Employee对象的一个实例:
点击(此处)折叠或打开
- public EmployeeDetailPage(Employee employee)
- {
- InitializeComponent();
- this.BindingContext = employee;
- }
虽然可以单独设置每个目标对象的BindingContext属性,但这不是必需的。 BindingContext是一个由其所有子项继承的特殊属性。 因此,当ContentPage上的BindingContext设置为Employee实例时,ContentPage的所有子项都具有相同的BindingContext,并且可以绑定到Employee对象的公共属性。
以下代码显示了在C#中执行数据绑定的示例:
点击(此处)折叠或打开
- public EmployeeDetailPage(Employee employeeToDisplay)
- {
- this.BindingContext = employeeToDisplay;
- var firstName = new Entry()
- {
- HorizontalOptions = LayoutOptions.FillAndExpand
- };
- firstName.SetBinding(Entry.TextProperty, "FirstName");
- ...
- }
ContentPage构造函数传递一个Employee对象的实例,并将BindingContext设置为要绑定的对象。一个Entry控件被实例化,并且Entry.Text属性和Source对象的FirstName属性之间的绑定被设置。在Entry控件中进行的更改将自动传播到employeeToDisplay对象。同样,如果对employeeToDisplay.FirstName属性进行更改,Xamarin.Forms绑定引擎也将更新Entry控件的内容。这被称为双向绑定。为了双向绑定工作,模型类必须实现INotifyPropertyChanged接口。
SetBinding方法有两个参数。第一个参数指定有关绑定类型的信息。第二个参数用于提供有关绑定到什么或如何绑定的信息。在大多数情况下,第二个参数只是一个持有BindingContext属性名称的字符串。以下语法用于直接绑定到BindingContext:
点击(此处)折叠或打开
- someLabel.SetBinding(Label.TextProperty, new Binding("."));
点语法告诉Xamarin.Forms使用BindingContext作为数据源,而不是BindingContext上的一个属性。 当BindingContext是一个简单的类型,如字符串或int时,这是有用的。
默认情况下,目标对象仅在创建绑定时才接收源对象的值。 为了使UI与数据源保持同步,当源对象发生更改时,必须有一种通知目标对象的方法。 该机制由INotifyPropertyChanged接口提供。 当底层属性值更改时,实现此接口将向任何数据绑定控件提供通知。
实现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))
- {
- // 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事件。 为避免不必要的事件触发,如果属性值不更改,则不会引发PropertyChanged事件。
请注意,在OnPropertyChanged方法中,propertyName参数使用CallerMemberName属性进行装饰。 这确保如果使用空值调用OnPropertyChanged方法,CallerMemberName属性将提供调用OnPropertyChanged的方法的名称。
Xamarin.Forms提供了许多不同的页面导航体验,具体取决于所使用的页面类型。 对于ContentPage实例,有两种导航体验:
CarouselPage,MasterDetailPage和TabbedPage类提供了替代的导航体验。 有关详细信息,请参阅导航。
NavigationPage类提供了一种分层导航体验,用户可以根据需要在页面,向前和向后浏览。 该类实现导航作为页面对象的先入先出(LIFO)堆栈。
在层次导航中,NavigationPage类用于浏览ContentPage对象的堆栈。 要从一个页面移动到另一个页面,一个应用程序会将一个新的页面推送到导航堆栈,在那里它将成为活动页面。 要返回上一页,应用程序将从导航堆栈弹出当前页面,并且新的最上面的页面将成为活动页面。
添加到导航堆栈的第一个页面被称为应用程序的根页面,以下代码示例显示了如何实现:
点击(此处)折叠或打开
- public App ()
- {
- MainPage = new NavigationPage(new EmployeeListPage());
- }
要导航到LoginPage,有必要在当前页面的Navigation属性中调用PushAsync方法,如下面的代码示例所示:
这将导致新的LoginPage对象被推送到导航堆栈,在那里它成为活动页面。点击(此处)折叠或打开
- await Navigation.PushAsync(new LoginPage());
可以通过按设备上的“后退”按钮从导航堆栈中弹出活动页面,无论这是设备上的物理按钮还是屏幕上的按钮。 要以编程方式返回上一页,LoginPage实例必须调用PopAsync方法,如以下代码示例所示:
点击(此处)折叠或打开
- await Navigation.PopAsync();
有关分层导航的更多信息,请参阅分层导航。
Xamarin.Forms提供对模式页面的支持。 模式页面鼓励用户完成任务完成或取消之前无法导航的独立任务。
模态页面可以是Xamarin.Forms支持的任何页面类型。 要显示模态页面,应用程序会将其推送到导航堆栈中,并将会成为活动页面。 要返回上一页,应用程序将从导航堆栈弹出当前页面,新的最上面的页面将成为活动页面。
导航导航方式由任何Page派生类型的Navigation属性公开。 Navigation属性还暴露了一个ModalStack属性,从中可以获取导航堆栈中的模态页面。 但是,没有任何模式堆栈操作的概念,或在模态导航中弹出到根页面。 这是因为这些操作在基础平台上不被普遍支持。
执行模式页面导航不需要NavigationPage实例。
要模式地导航到LoginPage,有必要在当前页面的Navigation属性中调用PushModalAsync方法,如下面的代码示例所示:
点击(此处)折叠或打开
- await Navigation.PushModalAsync(new LoginPage());
这将导致LoginPage实例被压入导航堆栈,在那里它成为活动页面。
可以通过按设备上的“后退”按钮从导航堆栈中弹出活动页面,无论这是设备上的物理按钮还是屏幕上的按钮。 要以编程方式返回原始页面,LoginPage实例必须调用PopModalAsync方法,如以下代码示例所示:
这会导致LoginPage实例从导航堆栈中移除,新的最上层页面变为活动页面。点击(此处)折叠或打开
- await Navigation.PopModalAsync();
有关模态导航的更多信息,请参阅模态页面。
这篇介绍性的文章应该可以让你开始编写Xamarin.Forms应用程序。建议的下一步包括阅读以下功能:
或者,使用Xamarin.Forms创建移动应用程序,Charles Petzold的一本书是了解更多关于Xamarin.Forms的好地方。有关详细信息,请参阅使用Xamarin.Forms创建移动应用程序。
本文介绍了Xamarin.Forms以及如何开始编写应用程序。 Xamarin.Forms是一个跨平台的本机支持的UI工具包抽象,允许开发人员轻松创建可以在Android,iOS,Windows和Windows Phone上共享的用户界面。使用目标平台的本地控件渲染用户界面,从而允许Xamarin.Forms应用程序为每个平台保留适当的外观。