评论删除后,数据将无法恢复
I have worked with Windows Phone 7 since it was in beta, so as you can imagine, I downloaded the Windows Phone 8 SDK as soon as it went live. For a bit of fun I decided to create a simple run-tracking application that showcases a number of these features ... and for an extra challenge do it all within 100 lines of code! (without resorting to writing compact and cryptic code).
Whilst it was perfectly possible to develop a run-tracking app with Windows Phone 7 (and there are a number of good examples in the Marketplace), the new features and capabilities of Windows Phone 8 can be used to make a much more feature-rich application.
NOTE: I originally published this article on the Nokia Developer Wiki, but thought I would share it on CodeProject also, where most of my other articles have been published.
This application has quite a basic UI, which is composed of full-screen map, which has the run statistics overlayed on top of it as shown in the screenshot below:
The application UI is defined in XAML as follows:
<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>
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
Before building and running the application, you have to include the mapping ‘capability’. To do this open up '''WPAppManifest.xml''', navigate to the Capabilities tab and check theID_CAP_MAPcheckbox. While you’re there, you may as well includeID_CAP_LOCATIONas well:
Capabilites are used to determine the phone features that your application uses so that users can more easily determine what an application does.
With these capabilities included, build and run the application and you should see the same UI that was illustrated above.
One of the improvements in the maps control is that it is fully vector-based (The Windows Phone 7 map is image-tile-based), this creates a much more smooth transition when the map is zoomed, and also allows for 3D transformations (as we will see a little later on). The map control also has a few other useful features for our running app, pedestrian-features and landmarks. These can be enabled as follows:
<!-- the map --> <maps:Map x:Name="Map" PedestrianFeaturesEnabled="True" LandmarksEnabled="True" ZoomLevel="16"/>
With these features enabled the map illustrates useful features such as stairs, crossings and 3D landmarks:
(By the way, I’m not counting the ~50 lines of XAML in my total lines-of-code count!)
The Windows Phone 8 maps have many more new features that I have not used in this application. You could for example use the newColorMode, which allows you to render a 'dark' map which is easier on the eyes in low light conditions. You could even make the run-tracking app choose theColorModebased on the time of day!
When the '''Start''' button is tapped the application tracks the user’s location using the phone’s built in GPS receiver, in order to mark their path on the map. It also times their run duration and generates various statistics of interest. We’ll start with the simpler of the two, timing the run. When the start button is clicked aDispatcherTimeris started and the time of the button tap recorded. On each timer ‘tick’ the label which indicates the elapsed run time is updated:
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"; } } }
With the above code in place, tapping the '''start''' button starts the timer.
The next step is to track the location whilst the timer is running. The Windows Phone API has aGeoCoordinateWatcherclass which fires aPositionChangedevent which can be used to track the user’s location. It is very easy to render the user’s movements on a map via aMapPolyLine, which is a line path which is defined in terms of geocoordinates. Each time the event is fired, a new point is added to the line as follows:
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); } }
With these few lines of extra code, the path of the user’s run is added to the map:
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; }
Runner’s do not measure pace in miles or kilometers per hour. Instead, pace is measured in terms of the time taken to travel a set distance. This method of measurement makes it much easier to determine your overall race time, e.g. if you are running at 4:00 minute-kilometers pace, you will complete a 5k race in 20 minutes.
NOTE: The code above uses a pretty basic calorie calculation, assuming a burn rate of 65 calories per kilometer. A more accurate calculation would incorporate the runner's weight and pace, and other environmental factors. I'll leave this as an exercise for the reader!
For developing applications that involve tracking a user’s location the emulator has some very useful features. You can record points along a route, then replay them at set intervals. You can also save the route as an XML file so that it can be replayed in future sessions:
It takes a while to create a realistic dataset that emulates a real run, but at least you only have to do this once!
Because of the vector nature of the Windows Phone 8 map it is possible to transform the view using the Pitch and Heading properties. The Pitch property sets the viewing angle of the map, providing a perspective rendering, rather than a top-down rendering, while the Heading property allows you to rotate the map. Most sat-nav systems use a combination of these effects to render the map so that it looks the same as the view directly in front of you. Many people find this type of map view much easier to understand (they do not have to perform rotate transforms in their head!).
Adding this feature to the running app is really easy, firstly setting the map Pitch is simply done in the XAML:
<!-- the map --> <maps:Map x:Name="Map" PedestrianFeaturesEnabled="True" LandmarksEnabled="True" Pitch="55" ZoomLevel="18"/>
评论删除后,数据将无法恢复
评论(12)
引用来自“leixu_txtek”的评论
引用来自“SamH”的评论
学习了……
引用来自“SamH”的评论
学习了……
引用来自“Ghost_”的评论
引用来自“棟梁”的评论
是否 Windows Phone 8 比較 Android 强大?
引用来自“棟梁”的评论
是否 Windows Phone 8 比較 Android 强大?