VS2017 中 Xamarin 的演练 – 第一部分
在微软 Xamarin in Visual Studio 教程的基础上,我已将其更新至 VS2017,并进行了一些进一步的改进。
引言
当我尝试使用微软的教程(https://docs.microsoft.com/en-au/visualstudio/cross-platform/build-apps-with-native-ui-using-xamarin-in-visual-studio)自学 Xamarin 时,遇到了困难。该教程非常基础,并且许多选项和库都已经发生了变化。因此,我决定创建这个迷你系列来扩展原有的指南。
许多提供指导的文章都非常基础。我想从 Xamarin 的基础知识开始,更新它们至当前(2017 年 6 月),然后开始扩展功能,使其不仅仅是一个“如何操作”指南,而是如何编写一个体面的应用程序——而不仅仅是示例代码片段。
设置、安装和模拟器技巧
我目前使用的是 Visual Studio 2017 社区版。
有关说明,请参阅 https://docs.microsoft.com/en-au/visualstudio/cross-platform/setup-and-install
我还应该提到,我遇到了一个奇怪的 bug,安装 VS2017 和 Xamarin 后无法启动 Windows。所以请务必先备份您的计算机,以防万一。
您需要从 http://openweathermap.org/appid 获取免费的 API 密钥。
我有一个 Windows Phone Lumia 1520 用于测试 UWP 版本,并且我正在使用 Android 模拟器(您可以在 Visual Studio 的 工具、Android、Android 仿真管理器菜单中找到它们)。
我确实在 Android 模拟器上遇到了一些问题,直到我简化了配置(ARM 手机、ARM 平板电脑、x86 手机和 x86 平板电脑)并全部设置为运行 7.1.1,API 级别 25。
我不是 Android 专家,所以我确信会有很多人提出比这更好的解决方案的建议。
在此编辑器中,您也可以启动模拟器。同样,我在模拟器上也遇到了一些问题(也许是因为我的机器只有 4GB 内存),但是只要我这样做,一切都正常:
- 手动启动模拟器并让它保持运行(而不是让 VS 尝试启动它);
- 手动构建然后部署到模拟器;
- 通过 VS 调试/运行应用程序。
我还没有尝试过 iOS。有机会时,我将在 iPhone 上安装 Xamarin Live Player,并更新这篇文章……
最后一点,对于 VS 和 Xamarin,XAML 目前不会像普通的 UWP / WPF 应用程序那样显示一个“屏幕”。但是,您可以通过打开“视图”、“其他窗口”、“Xamarin Forms Previewer”来查看应用程序在设计时的样子。
创建您的项目
文件、新建项目。在“Visual C#”模板部分,您应该找到(并选择)“跨平台”。在右侧选择“跨平台应用 (Xamarin)”,然后输入名称 WeatherApp 并点击“确定”。
在下一个屏幕上选择“空白应用”、“Xamarin.Forms”和“可移植类库 (PCL)”。
Visual Studio 现在将创建您的解决方案,并询问您要定位哪个版本的 Windows。我选择 Windows 10 November Update (10.0; Build 10586) 作为最低版本,Windows 10 Creators Update (10.0; Build 15063) 作为目标版本。
Visual Studio 会要求您输入 Mac 的详细信息以进行 iOS 开发…… 在您想要为 iOS 编译/部署/调试之前,可以忽略此步骤。
创建的解决方案将包含四个项目:
- WeatherApp
- WeatherApp.Android
- WeatherApp.iOS
- WeatherApp.UWP (通用 Windows)
注意:第一个项目以前被称为“WeatherApp (Portable)”。
再做一些整理工作……
在“解决方案资源管理器”中,右键单击 WeatherApp.Android 项目,然后转到“属性”。
- 在“Android Options”选项卡上,取消选中“使用快速部署 (仅限调试模式)”。
- 在“Application”选项卡上,确保“Compile using Android version”设置为 7.1,以匹配上面的模拟器设置。
- 保存并关闭“属性”。
在“解决方案资源管理器”中,右键单击“WeatherApp”(4 个项目)解决方案,然后选择“管理解决方案的 NuGet 程序包”。
在“浏览”选项卡的搜索框中,输入 Newton.Json,然后在右侧勾选“项目”并点击“安装”按钮。
您可能会收到一些弹出窗口,要求您“审阅更改”和“接受许可”。点击“确定”/“我接受”……
在搜索框中输入 Microsoft.Net.Http,然后点击“安装”按钮。您可能会收到一些弹出窗口,要求您“审阅更改”和“接受许可”。点击“确定”/“我接受”……
在撰写本文时,模板中存在一些过时的 NuGet 程序包,应予以更新。
转到“Consolidate”(如果等待应用更新)。清除搜索框,以便显示程序包,选择程序包并点击“安装”。
转到“Update”(即使那里没有数字表示有等待更新)。勾选“全选”并点击“更新”。同样,您可能会收到一些弹出窗口,要求您“审阅更改”和“接受许可”。点击“确定”/“我接受”……
此时,我收到一条消息,提示 Visual Studio 需要重新启动。点击“重新启动”,当 VS 重新启动且解决方案重新加载后,在“解决方案资源管理器”中,右键单击“WeatherApp”(4 个项目)解决方案,然后选择“管理解决方案的 NuGet 程序包”。转到“Update”选项卡,选择任何需要更新的程序包,然后完成更新过程。
此时,我建议您构建解决方案,启动模拟器或连接您的设备,部署项目,然后通过调试器进行运行。我的示例是使用 Android 项目(设置为启动项)到模拟器——但无论您选择哪个项目(UWP、Android 或 iOS)都没关系。
请注意,VS 中“播放”按钮旁边的模拟器选择与我之前启动并部署到的模拟器匹配。
要运行(例如)在我的 Windows Phone 上,选择 ARM 作为 CPU,WeatherApp.UWP 作为项目,然后运行(设备)。
我建议让模拟器保持运行(如果您正在使用一个),只需在 Visual Studio 中点击“停止”。
最后,我们准备开始编码了……
代码
这个应用程序实际会做什么?很简单,对于给定的邮政编码,应用程序将显示从 Open Weather Map 网站返回的多个天气属性。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WeatherApp
{
public class Weather
{
public string Title { get; set; }
public string Temperature { get; set; }
public string Wind { get; set; }
public string Humidity { get; set; }
public string Visibility { get; set; }
public string Sunrise { get; set; }
public string Sunset { get; set; }
public Weather()
{
//Because labels bind to these values, set them to an empty string to
//ensure that the label appears on all platforms by default.
this.Title = " ";
this.Temperature = " ";
this.Wind = " ";
this.Humidity = " ";
this.Visibility = " ";
this.Sunrise = " ";
this.Sunset = " ";
}
}
}
=> 这是一个简单的类,将保存从 Open Weather Map 网站返回的数据。构造函数将所有变量默认设置为空字符串。
接下来,我们需要一个类来将我们的请求传递给 Open Weather Map 网站并返回响应供我们的应用程序使用。
右键单击 WeatherApp 项目,然后选择 Add,然后选择 Class。将类命名为 DataService.cs 并点击“Add”。
using Newtonsoft.Json;
using System.Threading.Tasks;
using System.Net.Http;
namespace WeatherApp
{
public class DataService
{
public static async Task<dynamic> getDataFromService(string pQueryString)
{
HttpClient client = new HttpClient();
var response = await client.GetAsync(pQueryString);
dynamic data = null;
if (response != null)
{
string json = response.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject(json);
}
return data;
}
}
}
{"coord":{"lon":-103.14,"lat":39.69},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":58.96,"pressure":1016,"humidity":39,"temp_min":48.2,"temp_max":64.4},"visibility":16093,"wind":{"speed":6.93,"deg":140},"clouds":{"all":1},"dt":1497596100,"sys":{"type":1,"id":533,"message":0.0076,"country":"US","sunrise":1497612254,"sunset":1497666163},"id":0,"name":"Anton","cod":200}
using System;
using System.Threading.Tasks;
using WeatherApp;
namespace WeatherApp
{
public class Core
{
public static async Task<Weather> GetWeather(string pZipCode)
{
//Sign up for a free API key at http://openweathermap.org/appid
string key = "YOUR KEY HERE";
string queryString = "http://api.openweathermap.org/data/2.5/weather?zip="
+ pZipCode + ",us&appid=" + key + "&units=imperial";
//Make sure developers running this sample replaced the API key
if (key != "YOUR API KEY HERE")
{
throw new ArgumentException("You must obtain an API key from openweathermap.org/appid and save it in the 'key' variable.");
}
dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);
if (results["weather"] != null)
{
Weather weather = new Weather();
weather.Title = (string)results["name"];
weather.Temperature = (string)results["main"]["temp"] + " F";
weather.Wind = (string)results["wind"]["speed"] + " mph";
weather.Humidity = (string)results["main"]["humidity"] + " %";
weather.Visibility = (string)results["weather"][0]["main"];
DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
DateTime sunrise = time.AddSeconds((double)results["sys"]["sunrise"]);
DateTime sunset = time.AddSeconds((double)results["sys"]["sunset"]);
weather.Sunrise = sunrise.ToString() + " UTC";
weather.Sunset = sunset.ToString() + " UTC";
return weather;
}
else
{
return null;
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WeatherApp"
x:Class="WeatherApp.MainPage">
<StackLayout>
<StackLayout Margin="10,0,0,0" VerticalOptions="Start" HorizontalOptions="Start" WidthRequest="400" BackgroundColor="#545454">
<Label Text="Weather App" x:Name="lblTitle"/>
<StackLayout HorizontalOptions="Start" Margin="10,10,0,0" VerticalOptions="Start" WidthRequest="400">
<Label Text="Search by Zip Code" FontAttributes="Bold" TextColor="White" Margin="10" x:Name="lblSearchCriteria" VerticalOptions="Start"/>
<Label Text="Zip Code" TextColor="White" Margin="10" x:Name="lblZipCode"/>
<StackLayout Orientation="Horizontal" VerticalOptions="Start">
<Entry WidthRequest="100" x:Name="edtZipCode" VerticalOptions="Start"/>
<Button Text="Get Weather" x:Name="btnGetWeather" VerticalOptions="Start"/>
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout VerticalOptions="StartAndExpand">
<Label Text ="Location" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtLocation"/>
<Label Text ="Temperature" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtTemperature"/>
<Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtWind"/>
<Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtHumidity"/>
<Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtVisibility"/>
<Label Text ="Sunrise" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtSunrise"/>
<Label Text ="Sunset" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="" Margin="10,0,0,10" x:Name="txtSunset"/>
</StackLayout>
</StackLayout>
</ContentPage>
public MainPage()
{
InitializeComponent();
btnGetWeather.Clicked += btnGetWeather_Click;
}
private async void btnGetWeather_Click(object sender, EventArgs e) { if (!String.IsNullOrEmpty(edtZipCode.Text)) { Weather weather = await Core.GetWeather(edtZipCode.Text); if (weather != null) { txtLocation.Text = weather.Title; txtTemperature.Text = weather.Temperature; txtWind.Text = weather.Wind; txtVisibility.Text = weather.Visibility; txtHumidity.Text = weather.Humidity; txtSunrise.Text = weather.Sunrise; txtSunset.Text = weather.Sunset; btnGetWeather.Text = "Search Again"; } } }
=> 按钮按下代码调用函数来使用从网站返回的数据填充 weather 变量,然后用结果更新每个屏幕控件。
----
如果您现在构建并部署此项目,您将得到一个显示天气数据的可用应用程序!我不住在美国,我使用邮政编码 80801 进行测试。
好了,您就拥有了一个可以部署到 Windows UWP、Android 手机和 iOS 的应用程序——使用单一代码流!
通常,教程会在此时停止,但我认为这还不是一个应用程序,只是一个代码示例。
很快我将发布下一篇文章,并继续将其构建成一个真正的应用程序。
https://codeproject.org.cn/Articles/1192180/Walkthrough-for-Xamarin-in-VS2017-Part-Two