MvvmCross TipCalc - 第 2 步:创建 Android UI
TipCalc 教程 MvvmCross v3 - Hot Tuna 的第 2 步。
介绍
本文是 MvvmCross v3 - Hot Tuna! 的 `TipCalc` 教程的第 2 步。
迄今为止...
我们开始的目标是创建一个应用程序,以帮助计算在餐厅应该留下多少小费。
我们计划基于此概念创建一个 UI

为了实现这一点,我们构建了一个“Core
”可移植类库项目,其中包含
- 我们的“业务逻辑” - `ICalculation`
- 我们的 `ViewModel` - `TipViewModel`
- 我们的 App,它包含了应用的连接逻辑,包括启动指令
所以我们现在准备好添加我们的第一个用户界面了。
那么……让我们从 Android 开始吧。
要创建一个 Android `MvvmCross` UI,你可以使用 Visual Studio 的项目模板向导,但在这里,我们将“从零开始”构建一个新项目,就像我们为核心项目所做的那样。
创建一个新的 Android UI 项目
向你的解决方案中添加一个新项目——一个名为 `TipCalc.UI.Droid` 的“`Xamarin.Android`”应用程序。
在这里面,你会找到常规的 Android 应用程序结构
- Assets 文件夹
- Resources 文件夹
- Activity1.cs 文件
删除 Activity1.cs
没人真的需要一个 `Activity1`。 " src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" />
另外,删除 /resources/Layout 文件夹中的 Main.axml。
添加引用
添加对 CoreCross、Binding 和 MvvmCross 的引用 - PCL 版本
为新项目添加对可移植库的引用
- Cirrious.CrossCore.dll
- 核心接口和概念,包括跟踪、IoC 和插件管理
- Cirrious.MvvmCross.Binding.dll
- `DataBinding` 类 - 你将主要从 XML 中使用它
- Cirrious.MvvmCross.dll
- Mvvm 类 - 包括视图和 ViewModel 的基类
- Cirrious.MvvmCross.Plugins.Json.dll
- 添加一个 PCL `Newtonsoft.JSON.Net` 实现 - 我们的 Android UI 应用程序将使用它在 Activities (页面) 之间导航
通常,这些文件会位于类似 {SolutionRoot}/Libs/Mvx/Portable/ 的文件夹路径中。
添加对 CoreCross、Binding 和 MvvmCross 的引用 - Droid 特定版本
为新项目添加对 `Xamarin.Android` 特定库的引用
- Cirrious.CrossCore.Droid.dll
- Cirrious.MvvmCross.Binding.Droid.dll
- Cirrious.MvvmCross.Droid.dll
这些库都通过 Android 特定的附加功能扩展了其 PCL 对应部分的功能。
通常,这些文件会位于类似 {SolutionRoot}/Libs/Mvx/Droid/ 的文件夹路径中。
另外,在同一个文件夹中,您还需要添加
- System.Windows.dll - Android 版本
- 这增加了一些核心的 PCL 适配 - 一些“类型转发”,允许 PCL 库访问像 `System.Windows.ICommand` 这样的东西,即使在 `Xamarin.Android` 上实际上并没有任何 `System.Windows`。
添加对 TipCalc.Core.csproj 的引用
添加对你的 `TipCalc.Core` 项目的引用 - 这是我们在上一步中创建的项目,它包含了
- 你的 `Calculation` 服务
- 您的
TipViewModel
- 您的 App 连接
添加 MvvmCross Android 绑定资源文件
这个文件 - MvxBindingAttributes.xml - 可以在这里找到。
它需要被复制到你项目中的 /Resources/Values 文件夹。
这个文件的内容非常简单——但技术性很强。我们稍后会回来详细讨论这个文件中声明的节点和属性,以及它们如何启用声明式数据绑定。
现在,我们唯一会用到的行是核心的数据绑定属性:`MvxBind`。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MvxBinding">
**<attr name="MvxBind" format="string"/>**
<attr name="MvxLang" format="string"/>
</declare-styleable>
<declare-styleable name="MvxListView">
<attr name="MvxItemTemplate" format="string"/>
<attr name="MvxDropDownItemTemplate" format="string"/>
</declare-styleable>
<item type="id" name="MvxBindingTagUnique"/>
<declare-styleable name="MvxImageView">
<attr name="MvxSource" format="string"/>
</declare-styleable>
</resources>
添加 Setup 类
每个 MvvmCross UI 项目都需要一个 `Setup` 类。
这个类位于我们 UI 项目的根命名空间(文件夹)中,负责初始化 `MvvmCross` 框架和你的应用程序,包括
- 控制反转 (IoC) 系统
MvvmCross
数据绑定- 您的 App 及其
ViewModel
集合 - 你的 UI 项目及其视图集合
大部分功能都是自动为你提供的。在你的 `Droid` UI 项目中,你只需要提供
- 您的 App - 您与业务逻辑和
ViewModel
内容的连接 - 一些用于 Json.Net 插件和导航机制的初始化
对于 TipCalc
,Setup.cs 中需要的所有内容如下
using Android.App;
using Android.Content;
using Cirrious.MvvmCross.Droid.Platform;
using Cirrious.MvvmCross.Droid.Views;
using Cirrious.MvvmCross.ViewModels;
using TipCalc.Core;
namespace TipCalc.UI.Droid
{
public class Setup : MvxAndroidSetup
{
public Setup(Context applicationContext) : base(applicationContext)
{
}
protected override IMvxApplication CreateApp()
{
return new App();
}
protected override IMvxNavigationSerializer CreateNavigationSerializer()
{
Cirrious.MvvmCross.Plugins.Json.PluginLoader.Instance.EnsureLoaded(true);
return new MvxJsonNavigationSerializer();
}
}
注意:你可能想知道为什么 Json 导航是在你的 Setup 代码中初始化的,而其他大部分初始化都是自动完成的。其原因在于 MvvmCross 努力最小化对外部项目的依赖。通过不在 MvvmCross 内部引用 JSON.Net,开发者以后可以自由选择完全不同的序列化机制——例如,他们可以自由选择 `ServiceStack.Text`、`System.Xml.Serialization` 甚至是某些自定义的二进制序列化器。
添加您的 View
添加 Android 布局 XML (AXML)
本教程不打算介绍 Android XML 布局。
相反,我在这里只说最基本的内容。如果你是 Android 新手,你可以从很多地方了解更多关于 Android XML 的信息,包括官方文档。如果你有 XAML 背景——你是个 XAMLite——那么我会提供一些简单的 XAML-AXML 对比来帮助你。
为了实现基本布局
-
我们将在 /resources/Layout 文件夹中添加一个新的 AXML 文件 - View_Tip.axml。
-
我们将使用 Xamarin Android 设计器或 Visual Studio XML 编辑器来编辑它——设计器为我们提供可视化显示,而 VS 编辑器有时会提供 XML 智能感知。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> </LinearLayout>
-
我们将添加一个本地应用命名空间 - http://schemas.android.com/apk/res/TipCalc.UI.Droid - 这就像在 XAML 中添加命名空间一样。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res/TipCalc.UI.Droid" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> </LinearLayout>
-
注意这个“布局”默认已经是一个垂直的 `LinearLayout`——对于 XAMLites 来说,这个布局就像一个 `StackPanel`,但非常重要的是要指定垂直方向。
-
在这个布局中,我们将添加一些 `TextViews` 来提供一些 `static` 文本标签——对于 XAMLites 来说,这就像 `TextBlocks`。
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="SubTotal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Generosity" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Tip to leave" />
-
我们还会添加一个短而宽的 `View`,背景为黄色,以提供一点装饰。
<View android:layout_width="fill_parent" android:layout_height="1dp" android:background="#ffff00" />
-
我们将添加一些用于数据显示和输入的 `View`,并且我们将把这些 `View` 数据绑定到我们 `TipViewModel` 中的属性上。
-
一个用于输入 `SubTotal` 文本数据的 `EditText` - 对于 XAMLites 来说,这是一个 `TextBox`。
<EditText android:layout_width="fill_parent" android:layout_height="wrap_content" local:MvxBind="Text SubTotal" />
-
一个用于通过触摸/滑动输入 Generosity 的 `SeekBar` - 对于 XAMLites 来说,这就像一个 `ProgressBar`。
<SeekBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="40" local:MvxBind="Progress Generosity" />
-
我们将添加一个 `TextView` 来显示计算得出的小费
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" local:MvxBind="Text Tip" />
-
综合起来,它看起来像这样
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/TipCalc.UI.Droid"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="SubTotal" />
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="Text SubTotal" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Generosity" />
<SeekBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="40"
local:MvxBind="Progress Generosity" />
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="#ffff00" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tip to leave" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="Text Tip" />
</LinearLayout>
关于数据绑定语法
我们第一个示例中的每个数据绑定块看起来都差不多
local:MvxBind="Text SubTotal"
这行代码的意思是
- 数据绑定
- `View` 上的 `Text` 属性
- 到 `DataContext` 上的 `SubTotal` 属性——在这里 `DataContext` 将是 `TipViewModel`
- 因此
- 每当 `TipViewModel` 对 `SubTotal` 调用 `RaisePropertyChanged` 时,`View` 就应该更新
- 并且每当用户在 `View` 中输入文本时,`SubTotal` 的值就应该被设置到 `TipViewModel` 上
注意,这种 `TwoWay`(双向)绑定与 XAML 不同,在 XAML 中,默认的 `BindingMode` 通常只是 `OneWay`(单向)。
在后面的主题中,我们将回头向你展示更多数据绑定的选项,包括如何使用 `ValueConverters`,但目前我们所有的绑定都使用这种简单的 `ViewProperty ViewModelProperty` 语法。
添加 View 类
在 AXML 布局完成后,我们现在可以添加用于显示此内容的 C# `Activity`。对于有 XAML 背景的开发者来说,这些 `Activity` 类大致相当于 WindowsPhone 或 WindowsStore 应用程序中的 `Page` 对象——它们拥有“整个屏幕”,并有一个生命周期,这意味着任何时候只有一个 `Activity` 会被显示。
要创建我们的 `Activity` - 它也将是我们的 Mvvm View
-
在你的 `TipCalc.UI.Droid` 项目中创建一个 Views 文件夹
-
在这个文件夹中,创建一个新的 C# 类 - `TipView`
-
这个类将
-
继承自 `MvxActivity`
public class TipCalcView : MvxActivity
-
用 `Xamarin.Android Activity` 特性标记,将其标记为项目的主启动器 (`MainLauncher`)
[Activity(MainLauncher=true)]
-
提供一个新的 `ViewModel` 属性来指定它期望的 `ViewModel` 类型——即 `TipViewModel`
public new TipViewModel ViewModel { get { return (TipViewModel)base.ViewModel; } set { base.ViewModel = value; } }
-
使用 `OnViewModelSet` 从 AXML 加载其 `ContentView`——这将使用由 Android 和 Xamarin 工具生成的资源标识符。
protected override void OnViewModelSet() { SetContentView(Resource.Layout.View_Tip); }
-
因此,这个完整的类非常简单
using Android.App;
using Cirrious.MvvmCross.Droid.Views;
using TipCalc.Core;
namespace TipCalc.UI.Droid.Views
{
[Activity(Label = "Tip", MainLauncher = true)]
public class TipView : MvxActivity
{
public new TipViewModel ViewModel
{
get { return (TipViewModel) base.ViewModel; }
set { base.ViewModel = value; }
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
SetContentView(Resource.Layout.View_Tip);
}
}
}
Android UI 完成了!
此时,您应该能够运行您的应用程序。
当它启动时...您应该看到

如果你想让它“更漂亮”,可以尝试在你的 AXML 中添加一些属性——比如
android:background="#00007f"
android:textColor="#ffffff"
android:textSize="24dp"
android:layout_margin="30dp"
android:padding="20dp"
android:layout_marginTop="10dp"
在很短的时间内,你应该可以创建一个“有样式”的东西……

……但要让它看起来“漂亮”可能需要一些设计技巧!
继续
我们还可以做更多的事情来让这个用户界面更美观,让应用更丰富……但对于这第一个应用程序,我们暂时就到这里。
让我们继续 `Xamarin.iOS` 和 Windows!
文章
- MvvmCross TipCalc - 步骤 1:创建核心可移植应用程序
- MvvmCross TipCalc - 第 2 步:创建 Android UI
- MvvmCross TipCalc - 第 3 步:创建 iOS UI
- MvvmCross TipCalc - 第 4 步:创建 Windows Phone UI
- MvvmCross TipCalc - 第 5 步:创建 Windows Store UI
- MvvmCross TipCalc - 步骤 6:创建 WPF UI
- MvvmCross TipCalc - 回顾
历史
- 2013年3月22日 - 首次提交。