平台特性 Platform Feature 設計
對相依性注入容器宣告要註冊 地理位置 服務
在 .NET MAUI 中,想要取得裝置內的 GPS 座標,可以透過 .NET MAUI 所提供的 地理位置 物件來實現。要使用這個物件,可以透過相依性注入的方式來取得。
- 在專案根目錄下
- 找到並且打開 [MauiProgram.cs] 檔案
- 找到
#region 使用 Microsoft.Extensions.DependencyInjection 套件,註冊相關要用到的服務
- 在其下方加入底下程式碼
builder.Services.AddSingleton<IGeolocation>(Geolocation.Default);
底下為完成後的 [MauiProgram.cs] 檔案內容
namespace PrismMonkey;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UsePrismApp<App>(PrismStartup.Configure)
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#region 使用 Microsoft.Extensions.DependencyInjection 套件,註冊相關要用到的服務
builder.Services.AddSingleton<IConnectivity>(Connectivity.Current);
builder.Services.AddSingleton<IGeolocation>(Geolocation.Default);
#endregion
return builder.Build();
}
}
找出離你最近的猴子 : 修正 ViewModel
宣告要注入的 地理位置 欄位
- 在 [ViewModels] 資料夾下
- 找到並且打開 [MonkeyListPageViewModel.cs] 檔案
- 找到
#region 透過建構式注入的服務
- 在其下方加入底下程式碼
private readonly IGeolocation geolocation;
在建構式來注入 地理位置 服務物件
- 找到 MonkeyListPageViewModel 建構式方法
- 在其方法上加入 IGeolocation 參數,使用這樣程式碼
IGeolocation geolocation
- 完成後的該建構式簽章為
public MonkeyListPageViewModel(INavigationService navigationService,
IPageDialogService dialogService,
MonkeyService monkeyService, IConnectivity connectivity,
IGeolocation geolocation) { ... }
- 找到
this.connectivity = connectivity;
敘述 - 在其下方加入底下程式碼
this.geolocation = geolocation;
建立一個命令物件,用於執行找出最近猴子的程式碼
- 找到
#region 在此設計要進行命令物件綁定的屬性
- 在其下方加入底下程式碼
public DelegateCommand GetClosestMonkeyCommand { get; set; }
設計找出最近猴子的商業邏輯方法
- 找到
#region 在此設計該 ViewModel 的其他商業邏輯程式碼
- 在其下方加入底下程式碼
// 發現離你最近的猴子
private async Task FindClosestMokey()
{
if (IsBusy || Monkeys.Count == 0)
return;
try
{
// 裝置可能已快取裝置的最新位置,取得最後一個已知位置
var location = await geolocation.GetLastKnownLocationAsync();
if (location == null)
{
// 查詢裝置的目前位置
location = await geolocation.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Medium,
Timeout = TimeSpan.FromSeconds(30)
});
}
// 發現離你最近的猴子
// 方法 Location.CalculateDistance 會計算兩個地理位置之間的距離
var first = Monkeys.OrderBy(m => location.CalculateDistance(
new Location(m.Latitude, m.Longitude), DistanceUnits.Miles))
.FirstOrDefault();
await dialogService.DisplayAlertAsync("", first.Name + " " +
first.Location, "OK");
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to query location: {ex.Message}");
await dialogService.DisplayAlertAsync("Error!", ex.Message, "OK");
}
}
在建構式內,為 GetClosestMonkeyCommand 物件進行初始化
- 找到
#region 在此將命令屬性進行初始化,建立命令物件與指派委派方法
- 在其下方加入底下程式碼
#region 取得當前 GPS 位置,並且找出最接近猴子的命令
GetClosestMonkeyCommand = new DelegateCommand(async () =>
{
await FindClosestMokey();
});
#endregion
這裡將會宣告一個可以進行資料綁定的命令物件,該物件準備設計用來在 View 中的某個按鈕來進行綁定,一旦,使用者點選這個按鈕之後,將會開始執行這個按鈕所指定的委派方法。
底下為完成後的 [MonkeyListPageViewModel.cs] 檔案內容
namespace PrismMonkey.ViewModels
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using Prism.Navigation;
using Prism.Services.Dialogs;
using PrismMonkey.Helpers;
using PrismMonkey.Models;
using PrismMonkey.Services;
public class MonkeyListPageViewModel : INotifyPropertyChanged, INavigationAware
{
// 這裡是實作 INotifyPropertyChanged 介面需要用到的事件成員
// 這是要用於屬性變更的時候,將會觸發這個事件通知
public event PropertyChangedEventHandler PropertyChanged;
#region 透過建構式注入的服務
// 這是透過建構式注入的頁面導航的實作執行個體
private readonly INavigationService navigationService;
private readonly IPageDialogService dialogService;
private readonly MonkeyService monkeyService;
private readonly IConnectivity connectivity;
private readonly IGeolocation geolocation;
#endregion
#region 在此設計要進行資料綁定的屬性
/// <summary>
/// 要瀏覽的所有猴子集合紀錄
/// </summary>
public ObservableCollection<Monkey> Monkeys { get; set; } = new();
/// <summary>
/// 是否有觸發 下拉更新 手勢條件
/// </summary>
public bool IsRefreshing { get; set; }
/// <summary>
/// 是否正在忙碌要從網路上下載猴子清單的 JSON 內容
/// </summary>
public bool IsBusy { get; set; }
/// <summary>
/// 是否沒有正在忙碌要從網路上下載猴子清單的 JSON 內容
/// </summary>
public bool IsNotBusy => !IsBusy;
#endregion
#region 在此設計要進行命令物件綁定的屬性
public DelegateCommand GetClosestMonkeyCommand { get; set; }
public DelegateCommand<Monkey> GoToDetailsCommand { get; set; }
public DelegateCommand GetMonkeysCommand { get; set; }
#endregion
public MonkeyListPageViewModel(INavigationService navigationService,
IPageDialogService dialogService,
MonkeyService monkeyService, IConnectivity connectivity,
IGeolocation geolocation)
{
#region 將透過建構式注入進來的物件,指派給這個類別內的欄位或者屬性
this.navigationService = navigationService;
this.dialogService = dialogService;
this.monkeyService = monkeyService;
this.connectivity = connectivity;
this.geolocation = geolocation;
#endregion
#region 在此將命令屬性進行初始化,建立命令物件與指派委派方法
#region 取得當前 GPS 位置,並且找出最接近猴子的命令
GetClosestMonkeyCommand = new DelegateCommand(async () =>
{
await FindClosestMokey();
});
#endregion
#region 點選某個猴子之後,要進行頁面切換的命令
GoToDetailsCommand = new DelegateCommand<Monkey>(async monkey =>
{
// 若沒有取得猴子資訊,則不會有任何動作
if (monkey == null)
return;
NavigationParameters parameters = new();
parameters.Add(ConstantHelper.NavigationKeyMonkey, monkey);
#region 舊的頁面導航用法
//await navigationService.NavigateAsync(ConstantHelper.MonkeyDetailPage, parameters);
#endregion
#region 採用 Navigation Builder 的用法
// 參考文章 : https://github.com/PrismLibrary/Prism/issues/2283
await navigationService.CreateBuilder()
.WithParameters(parameters)
.AddNavigationSegment(ConstantHelper.MonkeyDetailPage)
.NavigateAsync();
#endregion
});
#endregion
#region 取得網路上最新猴子清單資訊的命令
GetMonkeysCommand = new DelegateCommand(async () =>
{
await ReloadMonkey();
});
#endregion
#endregion
}
#region 在此設計該 ViewModel 的其他商業邏輯程式碼
// 發現離你最近的猴子
private async Task FindClosestMokey()
{
if (IsBusy || Monkeys.Count == 0)
return;
try
{
// 裝置可能已快取裝置的最新位置,取得最後一個已知位置
var location = await geolocation.GetLastKnownLocationAsync();
if (location == null)
{
// 查詢裝置的目前位置
location = await geolocation.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Medium,
Timeout = TimeSpan.FromSeconds(30)
});
}
// 發現離你最近的猴子
// 方法 Location.CalculateDistance 會計算兩個地理位置之間的距離
var first = Monkeys.OrderBy(m => location.CalculateDistance(
new Location(m.Latitude, m.Longitude), DistanceUnits.Miles))
.FirstOrDefault();
await dialogService.DisplayAlertAsync("", first.Name + " " +
first.Location, "OK");
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to query location: {ex.Message}");
await dialogService.DisplayAlertAsync("Error!", ex.Message, "OK");
}
}
// 使用注入的 MonkeyService 物件,來取得遠端所有猴子 JSON 紀錄
private async Task ReloadMonkey()
{
// 若已經觸發這個命令委派方法,則無需繼續往下執行
if (IsBusy)
return;
// 保持良好習慣,對於使用 await 呼叫非同步方法,要捕捉例外異常,避免程式崩潰
try
{
// 透過 .NET MAUI Essentials 的 Connectivity 類別可讓您監視裝置網路狀況的變更、
// 檢查目前的網路存取,以及目前連線方式。
// 若現在無法連上 Internet ,則顯示與通知使用者,操作錯誤訊息
if (connectivity.NetworkAccess != NetworkAccess.Internet)
{
// 使用 Prism.Maui 提供的對話窗警訊物件,顯示此錯誤訊息
await dialogService.DisplayAlertAsync("No connectivity!",
$"Please check internet and try again.", "OK");
return;
}
IsBusy = true;
// 透過之前設計的猴子讀取遠端服務端點服務類別
// 取得網路上的所有猴子 JSON 內容
var monkeys = await monkeyService.GetMonkeysAsync();
if (Monkeys.Count != 0)
Monkeys.Clear();
foreach (var monkey in monkeys)
Monkeys.Add(monkey);
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to get monkeys: {ex.Message}");
await dialogService.DisplayAlertAsync("Error!", ex.Message, "OK");
}
finally
{
IsBusy = false;
IsRefreshing = false;
}
}
#endregion
#region 頁面導航將會觸發的方法
// 因為實作 INavigationAware 介面,需要建立這個方法
// 該方法將會用於當要離開此頁面的時候,會被觸發執行
public void OnNavigatedFrom(INavigationParameters parameters)
{
}
// 因為實作 INavigationAware 介面,需要建立這個方法
// 該方法將會用於當要導航到此頁面的時候,會被觸發執行
public void OnNavigatedTo(INavigationParameters parameters)
{
}
#endregion
}
}
找出離你最近的猴子 : 修正 View
- 在 [Views] 資料夾下
- 找到並且打開 [MonkeyListPage.xaml] 檔案
- 找到
Text="Get Monkeys" />
- 在其下方加入底下 XAML 標記
<Button
Grid.Row="1" Grid.Column="1"
Margin="8"
Command="{Binding GetClosestMonkeyCommand}"
IsEnabled="{Binding IsNotBusy}"
Text="Find Closest" />
底下為完成後的 [MonkeyListPage.xaml] 檔案內容
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:PrismMonkey.ViewModels"
xmlns:model="clr-namespace:PrismMonkey.Models"
x:DataType="viewmodel:MonkeyListPageViewModel"
x:Class="PrismMonkey.Views.MonkeyListPage"
Title="所有猴子清單">
<Grid
ColumnDefinitions="*,*"
ColumnSpacing="5"
RowDefinitions="*,Auto"
RowSpacing="0"
>
<CollectionView
Grid.ColumnSpan="2"
ItemsSource="{Binding Monkeys}"
BackgroundColor="Transparent"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Monkey">
<Grid Padding="10">
<Frame HeightRequest="125" >
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MonkeyListPageViewModel}},
Path=GoToDetailsCommand}" CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Grid Padding="0" ColumnDefinitions="125,*">
<Image
Aspect="AspectFill"
HeightRequest="125" WidthRequest="125"
Source="{Binding Image}"
/>
<VerticalStackLayout
Grid.Column="1"
Padding="10">
<Label Text="{Binding Name}" />
<Label Text="{Binding Location}" />
</VerticalStackLayout>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button
Grid.Row="1" Grid.Column="0"
Margin="8"
Command="{Binding GetMonkeysCommand}"
IsEnabled="{Binding IsNotBusy}"
Text="Get Monkeys" />
<Button
Grid.Row="1" Grid.Column="1"
Margin="8"
Command="{Binding GetClosestMonkeyCommand}"
IsEnabled="{Binding IsNotBusy}"
Text="Find Closest" />
<ActivityIndicator
Grid.RowSpan="2" Grid.ColumnSpan="2"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"
/>
</Grid>
</ContentPage>
修正 Android 需要使用定位權限
- 滑鼠右擊資料夾 [Platforms] > [Android]
- 從彈出功能表內點選 [加入] > [類別]
- 建立名稱為 [AssemblyInfo.cs] 檔案
- 使用底下 C# 程式碼,替換掉這個檔案內容
using Android.App;
#region 在這裡宣告 Android 系統中需要用到的權限 Permission
[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
[assembly: UsesFeature("android.hardware.location", Required = false)]
[assembly: UsesFeature("android.hardware.location.gps", Required = false)]
[assembly: UsesFeature("android.hardware.location.network", Required = false)]
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
#endregion
在 Android 平台執行專案
點選中間上方工具列的 [Windows Machine] 這個工具列按鈕旁的下拉選單三角形
從彈出功能表中,找到 [Android Emulators] 內的任何一個模擬器
接者,開始執行這個專案,讓他可以在 Android 模擬器出現
當出現 [所有猴子清單] 這個頁面後
點選下方的 [Get Monkeys] 按鈕
稍微等候一段時間,將會看到所有猴子清單物件出現在畫面上
點選下方的 [Get Monkeys] 按鈕
此時,畫面上將會出現 [Allow PrismMonkey to access this device's location?] 的對話窗
在此,點選 [While using the app]
接下來就會看到離你最近的猴子了
對相依性注入容器宣告要註冊 地圖 服務
在 .NET MAUI 中,想要將某個 GPS 座標使用手機上的地圖軟體,將其顯示出來,可以透過 .NET MAUI 所提供的 地圖 物件來實現。要使用這個物件,可以透過相依性注入的方式來取得。
- 在專案根目錄下
- 找到並且打開 [MauiProgram.cs] 檔案
- 找到
#region 使用 Microsoft.Extensions.DependencyInjection 套件,註冊相關要用到的服務
- 在其下方加入底下程式碼
builder.Services.AddSingleton<IMap>(Map.Default);
底下為完成後的 [MauiProgram.cs] 檔案內容
namespace PrismMonkey;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UsePrismApp<App>(PrismStartup.Configure)
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#region 使用 Microsoft.Extensions.DependencyInjection 套件,註冊相關要用到的服務
builder.Services.AddSingleton<IConnectivity>(Connectivity.Current);
builder.Services.AddSingleton<IGeolocation>(Geolocation.Default);
builder.Services.AddSingleton<IMap>(Map.Default);
#endregion
return builder.Build();
}
}
顯示猴子所在地地圖 : 修正 ViewModel
宣告要注入的 地理位置 欄位
- 在 [ViewModels] 資料夾下
- 找到並且打開 [MonkeyDetailPageViewModel.cs] 檔案
- 找到
#region 透過建構式注入的服務
- 在其下方加入底下程式碼
private readonly IMap map;
在建構式來注入 地理位置 服務物件
- 找到 MonkeyDetailPageViewModel 建構式方法
- 在其方法上加入 IMap 參數,使用這樣程式碼
IMap map
- 完成後的該建構式簽章為
public MonkeyDetailPageViewModel(INavigationService navigationService,
IPageDialogService dialogService, IMap map) { ... }
- 找到
this.dialogService = dialogService;
敘述 - 在其下方加入底下程式碼
this.map = map;
建立一個命令物件,用於執行顯示猴子所在地地圖的程式碼
- 找到
#region 在此設計要進行命令物件綁定的屬性
- 在其下方加入底下程式碼
public DelegateCommand OpenMapCommand { get; set; }
在建構式內,為 GetClosestMonkeyCommand 物件進行初始化
- 找到
#region 在此將命令屬性進行初始化,建立命令物件與指派委派方法
- 在其下方加入底下程式碼
#region 根據猴子 GPS 位置,顯示此位置地圖在螢幕上
OpenMapCommand = new DelegateCommand(async () =>
{
try
{
// 開啟並且顯示地圖
await map.OpenAsync(Monkey.Latitude, Monkey.Longitude, new MapLaunchOptions
{
Name = Monkey.Name,
NavigationMode = Microsoft.Maui.ApplicationModel.NavigationMode.None
});
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to launch maps: {ex.Message}");
await dialogService.DisplayAlertAsync("Error, no Maps app!", ex.Message, "OK");
}
});
#endregion
顯示猴子所在地地圖 : 修正 View
- 在 [Views] 資料夾下
- 找到並且打開 [MonkeyDetailPage.xaml] 檔案
- 找到
<VerticalStackLayout Padding="10" Spacing="10">
- 在其下方加入底下 XAML 標記
<Button
Grid.Row="1" Grid.Column="1"
Margin="8"
Command="{Binding GetClosestMonkeyCommand}"
IsEnabled="{Binding IsNotBusy}"
Text="Find Closest" />
在 Android 平台執行專案
點選中間上方工具列的 [Windows Machine] 這個工具列按鈕旁的下拉選單三角形
從彈出功能表中,找到 [Android Emulators] 內的任何一個模擬器
接者,開始執行這個專案,讓他可以在 Android 模擬器出現
當出現 [所有猴子清單] 這個頁面後
點選下方的 [Get Monkeys] 按鈕
稍微等候一段時間,將會看到所有猴子清單物件出現在畫面上
點選下方的 [Get Monkeys] 按鈕
當所有猴子出現在螢幕上後
隨選點選任何一個猴子
在畫面上將會看到一個 [Show on Map] 按鈕
請點選此按鈕
接下來就會看到猴子會出現在哪個地方的地圖了
沒有留言:
張貼留言