一百行程式碼寫一個WP8路線跟蹤應用

類別: IT

目錄

  • 介紹
  • 應用程式介面
  • 定時執行
  • 定位追蹤
  • 設定地圖Pitch和Heading屬性
  • 後臺位置追蹤
  • Live Tile
  • 總結

介紹

我從Windows Phone7 還在測試階段的時候就開始開發了,所以在Windows Phone 8 SDK開放後第一時間下載了。為了有趣,我決定建立一個簡單的跑步定位追蹤的應用來展示大量的特性,並且我將挑戰在100行程式碼內完成此功能(不使用緊湊和可讀性差的程式碼)

本文將通過我所開發的這個應用引導你深入探究  Windows Phone 8 中的以下特性:
  • 新增的地圖控制元件(擁有步行與三維路標功能)。
  • 如何跟蹤使用者的位置,包括當其他程式執行時在後臺跟蹤的情況。
  • 在地圖上新增線條註釋
  • 一些3D地圖的特性,設定 pitch 與 heading 屬性。
  • 使用新增的模板建立動態Tile

雖然使用Windows Phone 7 開發一個這樣的路線跟蹤應用也是完全可以(商城裡已經有很多很棒的例子了),不過Windows Phone 8 中新增的特性與功能可以讓它的功能更加豐富。

注意哦: 我最初將本文發表在諾基亞開發者百科(Nokia Developer Wiki)上,不過在CodeProject 上我也分享了,在那裡你可以找到我寫的其他一些文章

應用程式介面

這個程式的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"/>

    <!-- 步行統計 -->
    <Grid Background="#99000000" Margin="20" 
          VerticalAlignment="Bottom">
      <Grid Margin="20"
            util:GridUtils.RowDefinitions="40, 40, Auto"
            util:GridUtils.ColumnDefinitions="*, *, *, *">
          
        <!-- 距離 -->
        <TextBlock Text="Distance:"/>
        <TextBlock Text="0 km" Grid.Column="1" x:Name="distanceLabel"
              HorizontalAlignment="Center"/>

        <!-- 時間 -->
        <TextBlock Text="Time:" Grid.Column="2"/>
        <TextBlock Text="00:00:00" Grid.Column="3" x:Name="timeLabel"
              HorizontalAlignment="Center"/>

        <!-- 卡路里 -->
        <TextBlock Text="Calories:" Grid.Row="1"/>
        <TextBlock Text="0" Grid.Column="1" x:Name="caloriesLabel"
              HorizontalAlignment="Center" Grid.Row="1"/>

        <!-- 步幅 -->
        <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>
GridUtilsis 是我幾年前寫的一個工具類,可方便定義網格(Grid)的行列屬性(該類適用於 WPF,Silverlight 以及Windows Phone)。如果你依序從頭編寫這個應用,為了能使用地圖,就得加入下面的名稱空間定義:
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps" 

在構建與執行程式之前,你得先開啟地圖功能。開啟 '''WPAppManifest.xml''',找到 Capabilities 標籤然後選中 ID_CAP_MAP,這時ID_CAP_LOCATION也(自動)被選中:

Capabilites 用於決定你的應用中打算加入的手機功能,從而方便使用者瞭解這個應用能做些什麼。

選中以上功能後,構建執行程式你就可以看到如上所述的介面。

地圖控制元件的其中一項改進即它是完全基於向量的(Windows Phone 7中的地圖是基於點陣圖),這便使得地圖在縮放時的過渡更加平滑,而且還可以進行3D變換(稍後將會看到)。對我們的路線跟蹤應用而言,地圖控制元件還有其他一些有用的功能,如步行與路標。 可以通過如下的程式碼設定:

<!-- the map -->
<maps:Map x:Name="Map"
      PedestrianFeaturesEnabled="True"
      LandmarksEnabled="True"
      ZoomLevel="16"/>

經過以上設定,地圖將會顯示一些有用的東西,比如街道、路標等資訊:

(順便插一句,我可沒想把這50行XAML算到我的程式碼總行數裡哈 :-P)

Windows Phone 8中的地圖還有很多新特性我沒在這個程式裡用到。比如你可以用下新的ColorMode屬性,它能讓你渲染一幅‘暗色模式’的地圖以便在低光環境下檢視,甚至可以讓這個路線跟蹤應用根據當天的時間自動選擇ColorMode

定時執行

當"Start"按鈕按下後程式就開始的GPS接收器就開始追蹤使用者的位置來標註使用者的行動軌跡,如果有興趣的話還可以計算已經跑步的時間和統計各種資料,我們先從兩項功能中簡單的一項開始,定時執行.當我們點選開始按鈕後 DispatcherTimer就開始執行了並且記錄下按鈕按下的時間,另一個timer定時器來更新顯示已經執行了多長時間.

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"按鈕後定時器就開始執行了

位置追蹤

下一步需要做的就是在計時器執行的同時來記錄位置資訊。Window Phone的API 庫有GeoCoordinateWatcherclass 類, 其中的一個方法 PositionChangedevent 可以用來記錄使用者的位置和路徑。 而後用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();

    //初始化一個line類用來記錄路徑
    _line = new MapPolyline();
    _line.StrokeColor = Colors.Red;
    _line.StrokeThickness = 5;
    Map.MapElements.Add(_line);

    _watcher.PositionChanged += Watcher_PositionChanged;

    //..計時器程式碼在此處省略..
  }

  //.. 計時器程式碼此處被省略 ...

  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;
}
Runner's不去測量每小時的速度是多少英里或公里.相反,速度用來計算一段跑完距離裡所需的時間.這種計算方法可以更輕鬆的確定你整體的跑步時間,例如:如果你跑步的速度是四分鐘一公里,那麼你會在20分鐘後跑完五公里.

注意:上面的程式碼使用了一個非常簡單的卡路里消耗演算法,假設每跑一公里消耗65卡路里,一個更精確的演算法會根據跑步者的體重和速度和其他環境因素來進行計算,將這個功能作為一個小練習留給讀者.

對於開發應用程式,模擬器有一些包括跟蹤使用者的位置在內的非常有用的特性。你可以沿著路線記錄一些點,然後每隔一段時間重複一下,你也可以將這些模擬資料儲存成一個xml,以便以後重複執行.

這可能需要一定時間來建立一個真實的資料來模擬真實的執行情況,但是你至少有一次來做這件事.

設定地圖視角和指向

由於 Windows Phone8 地圖的向量性質,它可以使用視角和指向屬性轉換檢視。 視角屬性設定地圖的觀察角度,提供了一個透視的影象,而非一個自上而下的影象,而指向屬性則使你可以旋轉地圖。 多數衛星導航系統將這些特效組合起來呈現地圖,這樣它看起來就像直接在你的面前一樣。很多人覺得這樣的地圖比較容易理解(他們不需要在大腦裡旋轉地圖)。

你可以很容易的將這些功能新增到你的應用中,首先,在 XAML 中設定地圖視角:

<!-- the map -->
<maps:Map x:Name="Map"
      PedestrianFeaturesEnabled="True"
      LandmarksEnabled="True"
      Pitch="55"
      ZoomLevel="18"/> 

指向的計算有些複雜。在上節中,當前及過去的座標被用來計算行駛的距離和速度。這兩個座標同樣也可以用來計算指向,儘管計算更復雜。 幸運的是,我發現了一個包含了很多有用的地理定位工具的 .NET 庫,其中也包含了指向的計算。使用這個 .NET 擴充套件庫, 查詢和設定指向將十分簡單:

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 將會變得更加流暢。

在下面,你將看到在紐約中央公園中的指向和地圖視角的展示:

 

後臺位置跟蹤

WP7 可以在鎖屏後(繼續)執行前臺應用,這對運動跟蹤類應用來說是十分重要的一項功能,它可以讓使用者在鎖機後其位置資訊依然能被記錄。而WP8 則更進一步,應用可以在後臺進行跟蹤,也就意味著,當使用者在使用其他應用比如檢查郵件、聽音樂時,程式可以在後臺跟蹤記錄使用者的地理位置資訊。

為了開啟這個功能你得手動更改'''WMAppManifest.xml''', 在上面右鍵選擇“檢視程式碼”,然後定位到 Tasks 標籤新增如下程式碼:

<Tasks>
  <DefaultTask Name="_default" NavigationPage="MainPage.xaml">
    <BackgroundExecution>
      <ExecutionType Name="LocationTracking" />
    </BackgroundExecution>
  </DefaultTask>
</Tasks>

這就搞定了! 

程式開始在後臺執行的時候就會觸發RunningInBackground 事件,藉此可以顯示一個Toast 通知訊息,不過接下來的小節中我們會採用一種更加有趣的方式來告知使用者其正被追蹤記錄的情況。

活動瓷貼 

WP8 新增了許多瓷貼模板,而這裡我們將用到‘Iconic Template’。開啟 '''WMAppManifest.xml''' (這次得用視覺編輯器了!), 選中模板 Iconic template。

更新瓷貼狀態就跟發訊息通知一樣簡單。每次位置變更的時候就會執行下面的程式碼:

ShellTile.ActiveTiles.First().Update(new IconicTileData()
{
  Title = "WP8Runner",
  WideContent1 = string.Format("{0:f2} km", _kilometres),
  WideContent2 = string.Format("{0:f0} calories", _kilometres * 65),
});
如果你現在把程式 pin 到開始螢幕並使用寬瓷貼樣式,那麼當位置在後臺被跟蹤記錄時,瓷貼將更新如下:

新增完如上程式碼,這個應用就算完成了!

總結

Windows Phone 8 有很多非常酷的功能可以用來增強你的程式。在本文中我就展示了一個簡單的路線跟蹤應用是如何從中受益的。同樣,強大的框架和API 能讓你只用非常少的程式碼就可以寫出複雜的程式。

很顯然本文所說的應用還有待完善!不過你何不親自嘗試著改進下呢,試試用獨立儲存(Isolated Storage)記錄跑步歷史,或者增加一個統計資訊圖表,你也可以用下Windows Phone 8 API中其他新增的功能,比如說使用語音命令來控制開始/結束!祝玩得開心!

那麼該應用是否真的用了100行程式碼呢?你可以從這裡下載原始碼 WP8Runner.zip,然後就會發現 '''MainPage.xaml.cs'''恰好用了 100 行。








一百行程式碼寫一個WP8路線跟蹤應用原文請看這裡

推薦文章