一个 Windows Phone 8 跑步追踪应用程序,仅用 100 行代码实现






4.99/5 (60投票s)
本文介绍了一个Windows Phone 8跑步追踪应用的开发,该应用使用了Windows Phone 8的许多新特性。
目录
- 引言
- 应用用户界面
- 计时跑步
- 位置追踪
- 设置地图倾斜和朝向
- 后台位置追踪
- 动态磁贴
- 结论
介绍
我从Windows Phone 7 beta版本开始就一直在使用它,所以您可以想象,Windows Phone 8 SDK一上线我就下载了它。为了好玩,我决定创建一个简单的跑步追踪应用程序,展示这些特性中的一些……并且为了增加挑战,我将所有代码都写在100行之内!(不使用紧凑且晦涩的代码)。

- 新的地图控件,具有行人特征和地标特征。
- 如何追踪用户位置,包括在其他应用运行时在后台追踪用户位置。
- 在地图上添加线路标注。
- 一些3D地图功能,设置倾斜和朝向。
- 动态磁贴,利用一种新的磁贴格式。
虽然使用Windows Phone 7开发跑步追踪应用是完全可能的(并且市场上有许多优秀的例子),但Windows Phone 8的新功能和能力可以用来制作功能更丰富的应用程序。
注意:我最初在Nokia开发者Wiki上发布了这篇文章,但考虑到我的其他文章大多发布在CodeProject上,我也想在这里分享一下。
应用用户界面
这个应用程序有一个非常基本的UI,它由一个全屏地图组成,地图上叠加了跑步统计数据,如下图所示。
应用程序的UI定义如下XAML:
<Grid util:GridUtils.RowDefinitions="Auto, *">
<!-- title -->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/ApplicationIconLarge.png" Height="50"/>
<TextBlock Text="WP8Runner" VerticalAlignment="Center"
Margin="10 0 0 0"
FontSize="{StaticResource PhoneFontSizeLarge}"/>
</StackPanel>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!-- the map -->
<maps:Map x:Name="Map"
ZoomLevel="16"/>
<!-- run statistics -->
<Grid Background="#99000000" Margin="20"
VerticalAlignment="Bottom">
<Grid Margin="20"
util:GridUtils.RowDefinitions="40, 40, Auto"
util:GridUtils.ColumnDefinitions="*, *, *, *">
<!-- distance -->
<TextBlock Text="Distance:"/>
<TextBlock Text="0 km" Grid.Column="1" x:Name="distanceLabel"
HorizontalAlignment="Center"/>
<!-- time -->
<TextBlock Text="Time:" Grid.Column="2"/>
<TextBlock Text="00:00:00" Grid.Column="3" x:Name="timeLabel"
HorizontalAlignment="Center"/>
<!-- calories -->
<TextBlock Text="Calories:" Grid.Row="1"/>
<TextBlock Text="0" Grid.Column="1" x:Name="caloriesLabel"
HorizontalAlignment="Center" Grid.Row="1"/>
<!-- pace -->
<TextBlock Text="Pace:" Grid.Column="2" Grid.Row="1"/>
<TextBlock Text="00:00" Grid.Column="3" x:Name="paceLabel"
HorizontalAlignment="Center" Grid.Row="1"/>
<Button Content="Start"
Grid.Row="2" Grid.ColumnSpan="4"
Click="StartButton_Click"
x:Name="StartButton"/>
</Grid>
</Grid>
</Grid>
</Grid>
GridUtils
是一个实用类,几年前我写过,它提供了方便的简写方式来定义网格列和行(适用于WPF、Silverlight和WindowsPhone)。如果您想跟着我一步步从头构建这个正在运行的应用程序,那么为了添加地图,您需要包含以下命名空间定义:
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
在构建和运行应用程序之前,您必须包含地图的“功能”。要做到这一点,请打开'''WPAppManifest.xml''',导航到“Capabilities”(功能)选项卡,然后勾选ID_CAP_MAP
复选框。既然您在那里,也可以勾选ID_CAP_LOCATION
。
功能用于确定您的应用程序使用的手机功能,以便用户可以更轻松地了解应用程序的功能。
包含这些功能后,构建并运行应用程序,您应该会看到与上面所示相同的UI。
地图控件的一个改进是它是完全基于矢量的(Windows Phone 7的地图是基于图像瓦片的),这在地图缩放时创建了更平滑的过渡,并且也允许3D变换(我们稍后会看到)。地图控件还为我们的跑步应用提供了其他一些有用的功能,如行人特征和地标。这些可以如下启用:
<!-- the map -->
<maps:Map x:Name="Map"
PedestrianFeaturesEnabled="True"
LandmarksEnabled="True"
ZoomLevel="16"/>
启用了这些功能后,地图将显示楼梯、过路口和3D地标等有用特征。
(顺便说一句,我的总代码行数不包括大约50行的XAML!)
Windows Phone 8的地图还有许多我在此应用中未使用的功能。例如,您可以使用新的ColorMode
,它允许您渲染一个“黑暗”地图,在弱光条件下对眼睛更友好。您甚至可以让跑步追踪应用根据一天中的时间来选择ColorMode
!
计时跑步
当点击'''Start'''按钮时,应用程序使用手机内置的GPS接收器追踪用户位置,以便在地图上标记他们的路线。它还会计时跑步时长,并生成各种有趣的统计数据。我们将从两者中较简单的一个开始,即计时跑步。当点击开始按钮时,会启动一个DispatcherTimer
,并记录按钮点击的时间。在每次计时器“滴答”时,更新显示已用跑步时间的标签。
public partial class MainPage : PhoneApplicationPage
{
private DispatcherTimer _timer = new DispatcherTimer();
private long _startTime;
public MainPage()
{
InitializeComponent();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
}
private void Timer_Tick(object sender, EventArgs e)
{
TimeSpan runTime = TimeSpan.FromMilliseconds(System.Environment.TickCount - _startTime);
timeLabel.Text = runTime.ToString(@"hh\:mm\:ss");
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
if (_timer.IsEnabled)
{
_timer.Stop();
StartButton.Content = "Start";
}
else
{
_timer.Start();
_startTime = System.Environment.TickCount;
StartButton.Content = "Stop";
}
}
}
有了上述代码,点击'''start'''按钮即可启动计时器。
位置追踪
下一步是在计时器运行时追踪位置。Windows Phone API有一个GeoCoordinateWatcher
类,它会触发一个PositionChanged
事件,可用于追踪用户位置。通过MapPolyLine
(这是一个由地理坐标定义的线路路径)在地图上渲染用户移动非常容易。每次事件触发时,都会像下面这样向线路添加一个新点:
public partial class MainPage : PhoneApplicationPage
{
private GeoCoordinateWatcher _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
private MapPolyline _line;
private DispatcherTimer _timer = new DispatcherTimer();
private long _startTime;
public MainPage()
{
InitializeComponent();
// create a line which illustrates the run
_line = new MapPolyline();
_line.StrokeColor = Colors.Red;
_line.StrokeThickness = 5;
Map.MapElements.Add(_line);
_watcher.PositionChanged += Watcher_PositionChanged;
//.. timer code omitted ...
}
//.. timer code omitted ...
private void StartButton_Click(object sender, RoutedEventArgs e)
{
if (_timer.IsEnabled)
{
_watcher.Stop();
_timer.Stop();
StartButton.Content = "Start";
}
else
{
_watcher.Start();
_timer.Start();
_startTime = System.Environment.TickCount;
StartButton.Content = "Stop";
}
}
private void Watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
var coord = new GeoCoordinate(e.Position.Location.Latitude, e.Position.Location.Longitude);
Map.Center = coord;
_line.Path.Add(coord);
}
}
有了这几行额外的代码,用户跑步的路径就被添加到了地图上。
PositionChanged
事件处理程序可以进一步开发,以计算总跑步距离、消耗的卡路里和配速。这利用了GeoCoordinate.GetDistanceTo
方法,该方法可用于计算两个位置之间的距离。
private double _kilometres;
private long _previousPositionChangeTick;
private void Watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
var coord = new GeoCoordinate(e.Position.Location.Latitude, e.Position.Location.Longitude);
if (_line.Path.Count > 0)
{
// find the previos point and measure the distance travelled
var previousPoint = _line.Path.Last();
var distance = coord.GetDistanceTo(previousPoint);
// compute pace
var millisPerKilometer = (1000.0 / distance) * (System.Environment.TickCount - _previousPositionChangeTick);
// compute total distance travelled
_kilometres += distance / 1000.0;
paceLabel.Text = TimeSpan.FromMilliseconds(millisPerKilometer).ToString(@"mm\:ss");
distanceLabel.Text = string.Format("{0:f2} km", _kilometres);
caloriesLabel.Text = string.Format("{0:f0}", _kilometres * 65);
}
Map.Center = coord;
_line.Path.Add(coord);
_previousPositionChangeTick = System.Environment.TickCount;
}
跑步者不以每小时英里或公里来衡量配速。相反,配速是以在规定距离内花费的时间来衡量的。这种测量方式可以更容易地确定您的总体比赛时间,例如,如果您以4:00分钟/公里的配速跑步,您将在20分钟内完成一个5公里比赛。
注意:上面的代码使用了一个非常基本的卡路里计算,假设每公里消耗65卡路里。更准确的计算将纳入跑步者的体重和配速以及其他环境因素。我将留给读者作为练习!
对于开发涉及追踪用户位置的应用程序,模拟器有一些非常有用的功能。您可以沿着路线记录点,然后在设定的时间间隔内重放它们。您还可以将路线另存为XML文件,以便在将来的会话中重放。
创建模拟真实跑步的逼真数据集需要花费一些时间,但至少您只需要做一次!
设置地图倾斜和朝向
由于Windows Phone 8地图的矢量性质,可以使用Pitch(倾斜)和Heading(朝向)属性来变换视图。Pitch属性设置地图的观察角度,提供透视渲染,而不是俯视渲染,而Heading属性允许您旋转地图。大多数卫星导航系统使用这些效果的组合来渲染地图,使其看起来与您正前方视图相同。许多人发现这种地图视图更容易理解(他们不必在头脑中进行旋转变换!)。
为跑步应用添加此功能非常简单,首先在XAML中设置地图的Pitch即可。
<!-- the map -->
<maps:Map x:Name="Map"
PedestrianFeaturesEnabled="True"
LandmarksEnabled="True"
Pitch="55"
ZoomLevel="18"/>
计算朝向稍微复杂一些。在上一节中,我们使用当前位置和前一个位置来计算配速和行驶距离。这两个位置可用于计算朝向,尽管计算起来有点复杂。幸运的是,我找到一个包含有用地理定位实用程序的.NET库,其中包括一个计算朝向的实用程序。使用.NET Extra库,查找和设置朝向非常简单。
PositionHandler handler = new PositionHandler();
var heading = handler.CalculateBearing(new Position(previousPoint), new Position(coord));
Map.SetView(coord, Map.ZoomLevel, heading, MapAnimationKind.Parabolic);
另外,请注意,上面的代码使用了地图的SetView
方法,而不是独立设置每个属性。如果直接设置属性,地图状态会立即改变,这意味着视图会从一个位置/朝向“跳”到另一个位置/朝向。而SetView
会在一个位置过渡到另一个位置,产生一个更流畅的UI。
您可以在下面看到朝向和倾斜的使用,这是在纽约中央公园的一次跑步:
后台位置追踪
使用Windows Phone 7时,您可以在锁屏下运行前台应用程序,这对于运动追踪应用程序来说是一项非常重要的功能,它允许用户在手机锁定的同时仍然追踪他们的位置。Windows Phone 8更进一步,追踪地理位置的应用程序可以运行在后台,这意味着它们可以在用户使用其他应用程序时(例如收发邮件或播放音乐)继续追踪用户的位置。
要启用此功能,您必须手动编辑'''WMAppManifest.xml''',右键单击文件并选择'''View code'''(查看代码)。然后找到Tasks
元素并添加以下内容。
<Tasks>
<DefaultTask Name="_default" NavigationPage="MainPage.xaml">
<BackgroundExecution>
<ExecutionType Name="LocationTracking" />
</BackgroundExecution>
</DefaultTask>
</Tasks>
就是这样!
当应用程序在后台运行时,会触发RunningInBackground
应用程序事件。您可以使用此事件显示一个Toast通知,例如,但在下一节中,我们将介绍一种更有趣的方式来让用户了解持续的位置追踪。
动态磁贴
Windows Phone 8增加了更多磁贴模板,这里我们将使用新的“Iconic Template”(图标模板)。要选择模板,请打开'''WMAppManifest.xml'''(这次使用可视化编辑器!),然后选择TemplateIconic
模板。
更新磁贴状态就像发送通知一样简单。每次位置更改时,都会执行以下代码。
ShellTile.ActiveTiles.First().Update(new IconicTileData()
{
Title = "WP8Runner",
WideContent1 = string.Format("{0:f2} km", _kilometres),
WideContent2 = string.Format("{0:f0} calories", _kilometres * 65),
});
现在,如果您将应用程序固定到开始屏幕并使用宽磁贴格式,当位置在后台被追踪时,磁贴会更新。
有了最后一个更改的添加,跑步应用程序就完成了!
结论
Windows Phone 8有一些非常酷的新功能,可以让您扩展应用程序的功能。在本文中,我展示了一个简单的跑步追踪应用程序如何受益于这些新功能。此外,API和框架的表达能力允许您用很少的代码开发复杂的应用程序。
显然,这里演示的应用程序还不够完整!为什么不自己尝试进一步开发它呢?为什么不尝试使用孤立存储来记录您的跑步历史?或者将摘要统计数据添加为图表?您可以尝试使用其他一些新的Windows Phone 8 API,例如语音命令来控制每次跑步的开始/停止?玩得开心!
那么,这个应用真的只有100行代码吗?您可以下载源代码, WP8Runner.zip,亲眼看看'''MainPage.xaml.cs'''正好是100行。