65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin 问题与解答

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018年4月28日

CPOL

22分钟阅读

viewsIcon

38635

本文包含了一些可能的入门问题及其答案,旨在帮助您理解或掌握 Xamarin 的基础知识。

免责声明

本文包含了一系列与 Xamarin 技术相关的入门问题及其答案。这将帮助您回顾或获取关于它的基本知识。本文假设您已经使用 Xamarin 开发了一些应用程序,并严格假定您具有 Xamarin 及相关技术方面的实践知识。

Xamarin 简介

Xamarin 是微软的一项跨平台移动开发技术,我们可以使用 C# 语言,通过一套代码库(iOS、Android、UWP)开发原生应用。Xamarin 采用两种方法进行应用开发:Xamarin.Forms 和 Xamarin Native。Xamarin.Forms 使用 MVVM 和 XAML,而 Xamarin Native 使用原生 UI 技术和 MVC 或 MVVMCross 架构。

那么,现在让我们开始提问吧!

Xamarin.Forms 与 Xamarin Native 的区别

在以下情况下使用 Xamarin.Forms:

  • 需要较少的平台特定代码
  • 代码共享比自定义 UI 更重要
  • UI 不复杂

在以下情况下使用 Xamarin Native:

  • 需要大量平台特定代码
  • 自定义 UI 比代码共享更重要
  • 使用了许多平台特定 API

什么是 Xamarin.Forms,使用它有什么好处?

Xamarin.Forms 是一个跨平台工具包,它帮助我们通过 XAML 在所有支持的平台上共享业务逻辑和 UI。

它使用 MVVM 模式和 XAML UI 中的绑定来完成此任务。它为每个独立平台生成纯原生控件。使用 Xamarin.Forms,我们可以在所有支持的平台(如 iOS、Android 和 UWP)之间共享整个业务逻辑和 UI。它还提供了一种使用渲染器对任何原生控件进行平台特定定制的方式。因此,我们几乎可以跨平台共享大约 80-90% 的代码。我们可以说 Xamarin 是跨平台之王。

Xamarin 开发支持哪些语言?

C# 和 F#

Xamarin.Forms 项目的基本架构是什么?

Xamarin.Forms 可以包含在一个解决方案下的四个项目(根据需求会有所不同)。

  • .NET Standard、PCL 或共享项目
  • iOS 项目
  • Android 项目
  • UWP 项目

其中,.NET Standard、PCL 或共享项目包含所有 UI 和业务逻辑。

iOS、Android、UWP 包含平台特定代码,其中包含渲染器或依赖服务实现。

我们有多少种代码共享方式?

我们有三种代码共享方式:

  • 共享项目:在这里,如果需要,我们使用 #if 编译器指令编写平台特定代码。
  • 可移植类库(Portable Class Libraries):在这里,我们创建一个针对我们希望支持的平台的 PCL,然后使用接口和依赖服务来使用平台特定功能。
  • .NET Standard 库:它的工作方式类似于 PCL,并且需要接口才能使用平台特定功能。

PCL 和共享项目有什么区别?

  • PCL 有输出程序集作为 DLL,而共享项目没有输出程序集。
  • PCL 中的文件被视为 PCL 的一部分,而在共享项目中,文件被视为引用项目的一部分并编译到该程序集中。
  • PCL 包含清晰整洁的平台无关代码,而共享项目包含许多 #if 编译器指令以区分平台之间的代码。
  • 如果良好的项目架构是主要考虑因素,那么 PCL 是比共享项目更好的方法。
  • PCL 使用接口和 DependencyServices 来访问平台特定功能,而共享项目可以直接访问它们。
  • PCL 编译时错误较少,而共享项目在切换平台特定项目时有许多编译时错误的可能。
  • 如果需要较少的平台特定代码,则使用 PCL。如果需要大量平台特定代码,则使用共享项目。然而,这并非总是如此。选择权始终在于开发人员。

App.cs 类是什么?

App.cs 是应用程序的主类,它提供以下功能:

  • MainPage:它有助于设置应用程序的初始页面。
  • Properties Dictionary:它帮助我们在生命周期状态之间存储简单值。
  • Static Current Property:它提供当前应用程序对象的实例。

解释 Xamarin.Forms 应用程序的生命周期方法。

生命周期方法是一组在应用程序进入特定状态时执行的方法。以下是这些方法:

  • OnStart:应用程序从头开始时执行
  • OnSleep:应用程序每次进入后台时执行
  • OnResume:应用程序从睡眠状态回到前台时执行

XAML 编译器 (XAMLC) 的目的是什么?

使用 XAML 编译器,我们可以选择直接将 XAML 编译成中间语言 (IL)。

优点

  • 它在编译时检查 XAML 中的任何错误,从而在编译时通知用户任何错误。
  • 它消除了一些 XAML 元素的开销和实例化时间。
  • 它不将 XAML 文件包含到最终程序集中,因此它减小了程序集的大小。

默认情况下,它是禁用的,我们可以通过添加 XamlCompilation 属性,在程序集级别和类级别启用它,如下所示:

示例

Assembly Level:
using Xamarin.Forms.Xaml;
...
[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
namespace XamSampleApp
{
  ...
}

Class Level
using Xamarin.Forms.Xaml;
...
[XamlCompilation (XamlCompilationOptions.Compile)]
public class HomePage : ContentPage
{
  ...
}

什么是 XAML 命名空间声明?

XAML 命名空间是在 XAML 文件顶部声明的命名空间,用于声明该 XAML 将从哪些命名空间中使用元素。

当我们创建任何新的 XAML UI 时,根元素中总是提供两个声明。

以下是没有前缀的默认 xmlns 声明:

xmlns="http://xamarin.com/schemas/2014/forms"

第二个是使用 x 前缀的声明:

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

所有使用前缀的命名空间声明都是非默认声明。

假设我们想将视图模型与 XAML 绑定,并且该视图模型声明在 namespace "XamSample.ViewModels" 中,我们必须像下面这样在 XAML 顶部声明这个 namespace

xmlns:vm="clr-namespace:XamSample.ViewModels; assembly=XamSample.ViewModels"

然后,我们就可以在 XAML 中使用 vm 前缀访问这个 namespace 中的元素了。

什么时候我应该在 XAML 中与命名空间声明一起声明程序集名称?

如果类型定义在声明 XAML 文件的本地程序集中,则无需声明程序集名称。例如:

<ContentPage ... xmlns:local="clr-namespace:XamSample" ...>
  ...
</ContentPage>

但如果类型定义在不同的程序集中,则必须在命名空间声明中提供程序集名称。例如:

<ContentPage ... xmlns:behaviors="clr-namespace:Behaviors;assembly=BehaviorsLibrary" ...>
  ...
</ContentPage>

什么是 XAML 标记扩展?

XAML 标记扩展帮助我们扩展为控件属性提供值的能力,使其可以来自不同的源,而不仅仅是提供 string 字面量。

示例:当我们为某个控件分配颜色时,我们可能会这样写:

<BoxView Color="Red"/> or <BoxView Color="#FF0000">

这里,我们将 Color 属性作为 string 值提供,然后它被转换为 "Color" 类类型的值。

但是,您可能希望将 Color 属性设置为存储在 ResourceDictionary 中的值,或来自某个类的 static 属性,或来自同一 XAML 页面上某个其他元素的 Color 属性等。

所有这些选项都是可能的,如果我们使用 XAML 标记扩展。它只是表达 Element 属性的不同方式。任何用大括号括起来的属性设置都被简单地称为标记扩展。例如:

<BoxView Color="{StaticResource PrimaryThemeColor}" />

您知道多少种不同类型的 XAML 标记扩展?

有不同的标记扩展可以通过 namespace 声明的 x 前缀来访问,例如:

  • x:Static:用于在 XAML 元素中访问 static 属性、字段或 Enum 成员。
  • x:Reference:用于将某个命名元素的引用声明到同一 XAML 页面的其他元素中。
  • x:Type:用于将某个属性的类型设置为 System.Type 对象。
  • x:Array:用于构造某种特定类型的对象数组。
  • x:Null:用于将某个属性的值设置为 null

什么是 ResourceDictionary?

  • ResourceDictionary 用于定义可在整个 Xamarin.Forms 应用程序中多次重用的 XAML 资源。
  • XAML 资源是可多次使用的对象的定义。
  • ResourceDictionary 允许所有此类资源对象声明在一个地方。
  • 通常,我们可以在 ResourceDictionary 中定义 StylesControlTemplatesDataTemplatesColorsConverters
  • 在 XAML 中,可以使用 StaticResource 标记扩展访问这些资源。
  • ResourceDictionary 可以在 Element 级别(特定 Element 内部)、Page 级别(Page 内部)或 Application 级别(App.xaml 内部)声明。
  • 有关示例,请点击此处

Xamarin.Forms 中有多少种页面类型?

页面通常是一个可视化元素,它占据屏幕的全部或大部分空间,包含一个子元素。

  • 内容页(Content Page):它显示一个通常包含 ScrollViewStackLayout 的单一视图。
  • 主从页(Master Detail Page):它管理两个信息窗格,通常用于滑动面板/抽屉。
  • 导航页(Navigation Page):它管理页面的导航堆栈。
  • 选项卡页(Tabbed Page):当需要使用选项卡在子页面之间导航时使用。
  • 模板化页(Templated Page):它使用控制模板显示全屏内容。
  • 轮播页(Carousel Page):它允许在子页面之间进行滑动手势,类似于画廊。

什么时候会将 NavigationPage 用作 MainPage?

当我们想要页面的层次结构导航,例如向前或向后导航时,我们可以将第一个页面(例如:MyFirstPageXaml)实例化并包装在 NavigationPage 中,然后将其分配给 App.cs 类的 MainPage 属性。例如:

public App ()
{
    MainPage = new NavigationPage (new MyFirstPageXaml ());
}

这会导致 MyFirstPageXaml 页面实例被推入导航堆栈,并且该页面成为活动页面并充当应用程序的根页面。因此,它创建了一个以 LIFO(后进先出)方式推入的页面堆栈。建议我们只将“ContentPage”实例传递给 NavigationPage

页面中 InitializeComponent() 方法的目的是什么?

当我们向项目添加任何新的 XAML 页面时,此方法会自动生成。它实例化并初始化我们在 XAML 文件中定义的所有对象。它根据它们的父子关系将它们相互连接。它将我们在代码中定义的事件处理程序与我们在 XAML 中设置的事件关联起来。然后,它生成整个对象树,就像页面的内容一样。

在此调用之前,我们不应该访问页面的任何控件,因为在此调用之前,任何控件都未初始化或不存在,因此我们会得到一个异常。

您将如何从一个页面导航到另一个页面?

在第一个页面的某个按钮点击事件上,我们可以调用以下方法,它将导航到第二个页面。

await Navigation.PushAsync (new MySecondPageXaml (), true);

我们必须使用 ContentPage 类(XAML Page 的代码隐藏)中可用的 "Navigation" 属性。所以这种导航可以在页面的代码隐藏文件中编写。

我们可以在 ViewModel 中访问 Navigation 属性吗?

是的,就像下面的代码一样:

var navigation = Application.Current.MainPage.Navigation;

那么,我们如何从 ViewModel 执行导航?

您可以通过两种方式从 ViewModel 进行导航:

简单方式

ViewModel 中编写以下代码,我们直接访问应用程序的 MainPage 实例并访问 Navigation 属性。

Application.Current.MainPage.Navigation.PushAsync(new MyDestinationPageXaml());

非简单方式

在代码隐藏中为页面提供 BindingContext 时,您可以将 PageNavigation 属性实例传递到 ViewModel 构造函数中,然后将此参数分配给 ViewModelINavigation 属性。

然后,使用此 INavigation 属性实例导航到其他页面,如下例所示:

public partial class HomeView : ContentPage
{
    public HomeView ()
    {
        InitializeComponent();
        BindingContext = new HomeViewModel(Navigation);
    }
}

ViewModel:
public class HomeViewModel
{
    public INavigation _navigation; 

    public HomeViewModel (INavigation navigation)
    {
        _navigation = navigation;
        GoToAnotherPageCommand = new Command(async () => await GoToAnotherPageCommandExecute());
    }

    //now, to navigate on some Button click Command:
    private async Task GoToAnotherPageCommandExecute()
    {
        await _navigation.PushAsync(new MyDestinationPageXaml(), true);
    }
}

列举几个常用的布局控件

  • Frame:它包含一个单一元素作为子元素,默认填充为 20。
  • Grid:当 UI 组件需要排列成行和列时使用。
  • StackLayout:当 UI 组件需要水平或垂直排列时使用。
  • ScrollView:它在需要时为子元素启用滚动。它只有一个子元素。

还有其他布局控件,例如 AbsoluteLayoutRelativeLayoutContentViewContentPresenter 等。

我们有多少种 LayoutOptions?

它们是:

  • Start, StartAndExpand
  • Center, CenterAndExpand
  • End, EndAndExpand
  • Fill, FillAndExpand

那么,每个 LayoutOptions 都有“AndExpand”后缀的特殊含义是什么?

假设我们在 StackLayout 中添加了一些元素,并使用了上述任何一种 Expand 选项,那么所有子元素都将尝试占用更多可用空间。如果 StackLayout 有任何未使用的空间,那么所有请求通过在 HorizontalVertical LayoutOptions 中使用“AndExpand”后缀进行扩展的子元素将平等地共享未使用的空间。如果 StackLayout 中的所有空间都已被使用,那么“AndExpand”将无效。

Margin 和 Padding 属性有什么区别?

Margin 属性表示元素与其相邻元素之间的距离,用于控制元素的渲染位置及其相邻元素的渲染位置。Margin 可以在 LayoutView 类上指定。

Padding 属性表示 Element 及其子元素之间的距离,因此它用于将控件与其自身内容分开。Padding 值可以在 Layout 类上指定。

假设两个相邻元素的边距都设置为 20 像素,这两个元素之间的距离是多少?为什么?

在这种情况下,两个元素之间的距离将是 40 像素,因为 Margin 属性值是可加的。此外,如果同时应用了 MarginPadding,则元素与其内容之间的距离将是 Margin + Padding

Margin 和 Padding 属性的类型是什么?

这两个属性的类型都是 Thickness

Thickness 值可以为负,这可能会剪裁或过度绘制内容。

如何在两个控件之间添加/绘制分隔线?

我们可以使用 BoxView 在两个控件之间,如下例所示:

水平分隔线

<StackLayout Orientation="Vertical">
    <Label Text="Title"/>
    //This is a horizontal separator line between two labels
    <BoxView HeightRequest="1" BackgroundColor="Black" HorizontalOptions="FillAndExpand" />
    <Label Text="Description"/>
</StackLayout>

垂直分隔线

<StackLayout Orientation="Horizontal">
    <Label Text="Title"/>
    <BoxView HeightRequest="100" WidthRequest="1" BackgroundColor="Black" />
    <Label Text="Description"/>
</StackLayout>

设计 XAML 页面时应遵循哪些最佳实践?

  • 不要在代码隐藏中创建 UI。使用 XAML 来实现,因为 XAML 比代码更具可读性。
  • 避免使用 RelativeLayout。它会要求 CPU 执行更多工作。
  • 启用 XAML 编译器。它在编译时检查 XAML 中的任何错误并减少一些负载。
  • 使用正确的布局来实现所需的 UI。如果可以使用 Grid 实现相同的功能,则不要使用 StackLayoutGridStackLayout 更轻量。
  • 使用布局压缩来提高 Page 渲染性能。
  • 在没有必要时不要使用绑定。例如:如果 Button 标题不是动态的,则直接在 XAML 中静态给出。
  • 除非需要,否则不要设置布局的 VerticalOptionsHorizontalOptions 属性。
  • 始终尝试减小可视化树的大小。
  • 减小应用程序资源字典的大小。

什么是 ViewCell?有多少种内置单元格?

ViewCell 是一个小的独立元素,它代表 ListViewTable 中的单个项目。ViewCell 实际上不是一个视觉元素,它是一个创建视觉元素的模板的描述。

内置单元格列表:

  • TextCell:它是一个由标题/主要文本和详细信息/次要文本标签组成的单元格。
  • ImageCell:它基本上是一个 TextCell,但左侧还包含一个 Image 组件。
  • SwitchCell:此单元格由 Label 和一个切换开关组成。
  • EntryCell:此单元格包含一个 Label 和一个单行 textbox,可用于输入数据。

为什么我们需要创建自定义 ViewCell?

内置单元格实际上允许我们在某些简单场景下在列表中显示一些数据。但是,在某些实际场景中,这些简单的单元格无法满足我们的所有需求。例如,假设我们想要在单元格右侧显示一些标签、2 个按钮和一张图像。这种场景无法使用内置单元格实现。因此,我们必须进行一些自定义。在 ListViewDataTemplate 中,我们可以简单地将我们自己的控件集包装在 ViewCell 标签中。

示例

<ListView RowHeight="60">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Orientation="Horizontal" HorizontalOptions="Fill">
                    <StackLayout Orientation="Vertical">
                        <Label Text = "{Binding Name}" />
                        <Label Text = "{Binding Email}" />
                    </StackLayout>
                    <Image Source="{Binding ProfileImage}" HorizontalOptions="End" />
                    <Button Text="Delete" WidthRequest="100" HeightRequest="50" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

如何在运行时在同一个 ListView 中渲染不同类型的 ViewCell?

Xamarin.Forms 实现了 DataTemplateSelector(在 Xamarin.Forms V 2.1 中引入),它提供了一种在运行时自定义单元格显示方式的方法。例如,如果我们要创建一个显示聊天消息的 ListView,那么我们将传入消息显示在左侧,将传出消息显示在右侧,并带有不同的颜色。我们可以使用 DataTemplateSelector 实现此功能。

我们必须重写 DataTemplateSelector 类,并为传入和传出消息设计不同的 DataTemplate,然后在运行时,我们可以根据绑定数据决定为 ListView 项目选择哪一个。

欲了解更多详情,请点击此处。

如何提高 ListView 的性能?

  • 不要将 ListView 放在 ScrollView 内部,因为 ListView 有自己的 Scrollview
  • 尽可能使用内置单元格,如 TextCellEntryCellImageCellSwitchCell,而不是创建自定义 ViewCell
  • 减少使用 Cell.ForceUpdateSize 方法。它会降低性能。
  • 避免根据 BindingContext 更改单元格布局。
  • 避免深度嵌套布局。使用 AbsoluteLayoutGrid
  • 避免使用 Fill 以外的特定 LayoutOption
  • 如果您的单元格包含复杂的 UI,则使用自定义渲染器。

如何存储简单的键值数据?

Xamarin.Forms 的 Application 类公开了 Application.Current.Properties Dictionary,用于存储简单的键值对数据。Properties 字典使用 string 键并存储一个对象值。

示例

///Store Value
Application.Current.Properties ["UserId"] = loggedInUser.Id;

///Get Value
if (Application.Current.Properties.ContainsKey("UserId"))
{
    var userId = Application.Current.Properties ["UserId"] as int;
    // do something with userId
}

什么是 Behaviors,并举例说明我们应该在哪里使用 Behaviors?

Behaviors 帮助我们在不创建子类的情况下为用户界面控件添加功能。所需功能使用 Behavior 类实现,并附加到该控件上,就好像它是该控件的一部分一样。

我们可以使用 Behaviors 的示例包括:

  • Entry 中的电子邮件验证器
  • 使用 Tap 手势创建评分控件
  • 控制动画
  • 为控件添加一些效果

如何将视图的事件与命令绑定?

我们可以使用 EventToCommandBehavior 类来实现此目的。当指定事件触发时,它会调用一个命令。

示例

<ListView ItemsSource="{Binding Employees}">
  <ListView.Behaviors>
    <local:EventToCommandBehavior            
           EventName="ItemSelected"
           Command="{Binding OutputAgeCommand}"
           Converter="{StaticResource SelectedItemConverter}" />  
  </ListView.Behaviors>
</ListView>

我们可以在图像点击时绑定命令吗?

是的,Xamarin.Forms 提供了 GestureRecognizer,例如 TapPinchPan GestureRecognizer。我们必须在 Image 上使用 TapGestureRecognizer 并将命令绑定到它,如下所示:

<Image Source="profile_pic.png" Margin="25,3,25,0">
    <Image.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding ProfileImageTapCommand}" NumberOfTapsRequired="1"/>
    </Image.GestureRecognizers>
</Image>

什么是自定义渲染器,它的目的是什么?

自定义渲染器提供了一种方法,可以针对特定平台自定义 Xamarin.Forms 控件的外观、感觉和行为。因此,它使我们能够原生自定义任何 Xamarin.Forms 可视元素。此外,如果 Xamarin.Forms 无法实现某些可视化元素的功能,我们可以使用渲染器来实现它。

什么是 Effects?什么时候应该使用 Effects 而不是 Custom Renderers?

使用 Effects,我们可以自定义每个平台上的原生控件。同样的功能也可以通过 Custom Renderers 实现,但有时建议使用 Effects 而不是 Custom Renderers。

  • 当控件有小的样式更改时,建议使用 Effects。然而,使用 Custom Renderer 对于这种小的更改可能会很繁重。
  • 实现 Effects 比 Custom Renderers 更简单且可重用。
  • Effects 可以参数化,使其更具可重用性。

ControlTemplate 和 DataTemplate 有什么区别?

ControlTemplate 决定了 Control 的外观,也就是说它定义了 Control 的表示样式。例如:Button 可以包含 ImageText

DataTemplate 决定了底层数据的视觉结构,也就是说您希望如何表示数据。

什么是触发器?有多少种触发器?

触发器允许我们在 XAML 中声明动作,当特定控件属性满足特定条件或特定事件发生时,这些动作会改变控件的外观。

我们可以在资源字典中的控件级别、页面级别或应用程序级别添加触发器。触发器有四种类型。

  • 属性触发器(Property Trigger):当控件上的属性设置为特定值时执行
  • 数据触发器(Data Trigger):它类似于属性触发器,但它利用了数据绑定
  • 事件触发器(Event Trigger):当控件上发生某个指定事件时触发
  • 多重触发器(Multi Trigger):允许在发生动作之前设置多个触发条件

有关触发器的更多详细信息,请点击此处

如何在 Xamarin.Forms 中显示静态 HTML 字符串?

我们可以使用 WebView 控件来显示 static HTML stringWebView 可以根据平台支持,用于显示网站、HTML 字符串、文档、本地文件。

在 XAML 中添加 WebView 控件,如下所示,并将包含 HTML string 的属性绑定到 HtmlWebViewsourceHtml 属性,如下所示:

<WebView VerticalOptions="FillAndExpand" >
    <WebView.Source>
        <HtmlWebViewSource Html="{Binding HtmlString}"/>
    </WebView.Source>
</WebView>
public class MyWebViewModel : BaseViewModel
{
    public string HtmlString
    {
        get
        {
            return @"<html><body>
                        <h1>HTML in WebView</h1>
                        <p>Hello Xamarin</p>
                    </body></html>";
        }
    }
}

什么是 DependencyService?描述其实现步骤。

DependencyService 用于从 PCL 或共享项目调用某个功能的本地平台特定实现。因此,它帮助我们做任何原生应用能做的事情。

步骤

  • 接口:在 PCL 或共享项目中为特定功能定义一个接口。
  • 按平台实现:在平台特定项目中添加类,并通过继承该接口在该类中实现该接口。
  • 注册:通过为其提供元数据属性,将每个平台特定类注册到 DependencyService

现在,我们可以通过 DependencyService 在 PCL 或共享项目中访问此类的原生实现。

我们如何在 XAML 中提供平台特定的样式或值?

我们可以在 XAML 中使用 OnPlatform 标签来实现这一点。OnPlatform 标签提供了一种直接在 XAML 中声明某些属性的平台特定值的方法。

示例:如果我们要为每个平台的 Button 提供不同的字体大小,可以通过以下方式实现:

Old Way:
<Button VerticalOptions="CenterAndExpand" Text="Save">
    <Button.FontSize>
        <OnPlatform x:TypeArguments="x:Double" iOS="15" Android="13" WinPhone="14" />
    </Button.FontSize>
</Button>

New Way:
<Button VerticalOptions="CenterAndExpand" Text="Save">
    <Button.FontSize>
        <OnPlatform x:TypeArguments="x:Double">
            <On Platform="iOS">15</On>
            <On Platform="Android">13</On>
            <On Platform="WinPhone">14</On>
        </OnPlatform>
    </Button.FontSize>
</Button>

如何设计手机和平板电脑之间不同的布局或功能?

Xamarin.Forms 提供了 Device.Idiom 枚举,通过它我们可以调整手机和平板电脑之间的设计/布局/功能。TargetIdiom enum 提供了以下值来区分各种设备:

Phone、Tablet、Desktop、TV、Unsupported

<Grid VerticalOptions="FillAndExpand">
   <Grid.ColumnSpacing>
      <OnIdiom x:TypeArguments="x:Double" Phone="20" Tablet="40"/>
   </Grid.ColumnSpacing>  
   <Grid.Padding>
      <OnIdiom x:TypeArguments="Thickness" Phone="10, 10, 10, 0" Tablet="20, 20, 20, 0"/>
   </Grid.Padding>
  <!-- Grid Content -->
</Grid>

In Code:
if (Device.Idiom == TargetIdiom.Phone) {
    // layout views vertically
} else {
    // layout views horizontally tablet or desktop
}

如何在代码隐藏中执行绑定?

每个 BindableObject 都有一个扩展方法,名为 SetBinding,它接受 View 的可绑定属性以及您要绑定到的 ViewModel 中的相应属性。

在以下示例中,我尝试将 EmployeeViewModel 中的 TitleFirstNameLastName 属性绑定到代码隐藏中相应 BindableObjectText 属性。

EmployeeDetailPage.cs Backend:

public partial class EmployeeDetailPage : ContentPage
{
    public EmployeeDetailPage()
    {
        BindingContext = new EmployeeViewModel();
        InitializeComponent();

        //Label
        lbl_Title.SetBinding(Label.TextProperty, "Title");
        
        //Textboxes
        tb_FirstName.SetBinding(Entry.TextProperty, "FirstName", BindingMode.TwoWay);
        tb_LastName.SetBinding(Entry.TextProperty, "LastName", BindingMode.TwoWay);         
    }
}

什么是视图到视图绑定?

视图到视图绑定意味着将一个控件的一个属性值绑定到 XAML 文件中另一个控件的属性。

示例:如果我们想根据 Slider 的值旋转 Label,那么我们可以将 Label 的 "Rotation" 属性与 Slider 的 "Value" 属性绑定,如下所示:

对于这种情况,我们不需要显式创建 ViewModel 属性和绑定。

<StackLayout>

    <Label Text="Rotate Me"
        BindingContext="{x:Reference Name=slider}"
        Rotation="{Binding Path=Value}"
        FontSize="Large"
        HorizontalOptions="Center"
        VerticalOptions="CenterAndExpand" />

    <Slider x:Name="slider"
        Maximum="360"
        VerticalOptions="CenterAndExpand" />

    <Label BindingContext="{x:Reference slider}"
        Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
        FontSize="Large"
        HorizontalOptions="Center"
        VerticalOptions="CenterAndExpand" />

</StackLayout>

您有多少种方法可以将 ViewModel 与 XAML 绑定?

我们可以直接在 XAML 中绑定 ViewModel,也可以在 XAML 的后端 .cs 文件中绑定它。

当我们在 XAML 中绑定 ViewModel 时,这被称为 XAML 中的 View-First Construction。

当我们在代码隐藏中绑定 ViewModel 时,这被称为代码隐藏中的 View-First Construction。

示例

Binding ViewModel in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:vm="clr-namespace:XamSample.ViewModels; assembly=XamSample.ViewModels" 
             x:Class="XamSample" 
             Title="Home Page"> 
    <ContentPage.BindingContext> 
        <vm:HomeViewModel/> 
    </ContentPage.BindingContext> 
    <StackLayout> 
        <Label Text="{Binding Title}"/> 
    </StackLayout> 
</ContentPage>

Binding ViewModel in backend .cs file:
public partial class HomePage : ContentPage
{
    public HomePage()
    {
        BindingContext = new HomeViewModel();
        InitializeComponent();
    }
}

在后端 .cs 文件中绑定 ViewModel 有什么好处吗?

是的,我们可以在 XAML 的后端 .cs 文件中自定义 ViewModel 的初始化。假设我们想用一些动态数据初始化它。因此,如果您的页面在加载之前需要一些动态数据传递到页面中,您需要将其传递到 ViewModel 的参数化构造函数中。现在将此 ViewModel 实例分配给后端 .cs 文件中 PageBindingContext。因此,它有助于我们用一些数据构建 ViewModel

示例

public partial class HomePage : ContentPage
{ 
    public HomePage(int userId) 
    { 
        //Ex. I want to pass the logged in user id into HomeViewModel then,        

        BindingContext = new HomeViewModel(userId); 
        InitializeComponent(); 
    } 
}

我们真的可以将参数化 ViewModel 实例声明为 XAML 中的 BindingContext 吗?

是的,但我们只能传递 static 值。我们可以使用 x:Arguments 属性。例如:

<ContentPage.BindingContext>
    <vm:HomeViewModel>
        <x:Arguments>
            <x:Int32 >1234</x:Int32> //This is passed as a static value as an argument 
                                     //to the parametrized construction of ViewModel in XAML.
        </x:Arguments>
    </vm:HomeViewModel>
</ContentPage.BindingContext>

public class HomeViewModel : BaseViewModel
{
    //Parametrized initialization of viewmodel
    public HomeViewModel(int employeeId)
    {
         //Here you will get 1234 value in the employeeId parameter.
    }
}

这要求 ViewModel 构造函数参数与 XAML 中提供的参数的 Type 和数量匹配。

我们有多少种数据绑定方式?

我们有四种方式可以将数据绑定到 BindableObject

  1. Default:当我们使用默认绑定模式时,它表示为 OneWay 绑定,这意味着数据更改从源(在本例中为 ViewModel)传播到 View 元素。
  2. OneWay:它反映了从源到 View 元素的更改,如上所述。示例:Label 使用 OneWay 绑定。
  3. OneWayToSource:它反映了从目标 (BindableObject) 到源 (ViewModel) 的更改。这主要用于只读可绑定属性。
  4. TwoWay:它反映了源和目标之间的双向更改。示例:Entry 通常使用 TwoWay 绑定。

INotifyPropertyChanged 的目的是什么?

它通知客户端指定属性的值已更改。通常,我们将此接口实现到绑定到目标 UI 元素的实体(ViewModel)中。

什么是 MessagingCenter?

使用 MessagingCenter,我们可以使应用程序的 ViewModel 或其他组件相互通信,而无需了解彼此。因此,它将发送方和接收方彼此解耦。它只是一个简单的消息契约,接收对象必须 SubscribeUnsubscribe 发送方发送的 Message

示例:假设我们想在一个 Page 中编辑一些详细信息,并想通知其他页面此更新,那么我们可以发送 Message,订阅此 Message 的其他页面将通过此 Message 接收通知,因此我们可以根据它在其他页面中采取适当的行动。

那么,MessagingCenter 和 Events 有什么区别?

事件在对象之间建立了强耦合,意味着 SenderReceiver 必须相互了解。由于这种耦合,有时资源不会从内存中释放,我们必须取消订阅事件才能正确释放资源。

MessagingCenter 解耦了对象,因此对象之间没有这种依赖关系,因此当不再需要时它们就会被释放。但是,过多使用 MessagingCenter 会使开发人员难以理解谁发送了它以及何时发送的,从而也难以调试代码。因此,我们应该明智地使用它们。

如何仅为特定平台调用特定方法?

我们可以使用 Device.RuntimePlatform 枚举来检查平台,如下所示:

if (Device.RuntimePlatform == Device.iOS)
{
    //Call the iOS Specific Method here
}


目前就这些了。除了这些之外,可能还有更多好的问题,但列表可能会更长。我仍然尝试涵盖大多数可以帮助您了解 Xamarin 基础知识的基本问题。如果您心中有可以帮助他人的好问题,您可以在下面的评论中分享。我很乐意将它们添加到此列表中。

© . All rights reserved.