Bill 叔某天跟我介紹了 Uno Platform 的好處,讓我想要寫一篇來介紹我移植 UWP app 到 Xamarin 的經驗。
有關於 Uno Platform 的介紹,建議先閲讀Bill 叔寫好的:Uno Platform 偵查隊 系列文章。
先列出開放時遇到的問題:
- Uno Platform Solution Templates 使用最新的 Uno.UI,但問題很多,建議使用 1.41.1-dev.134;
- 如果遇到 Resource.designer.cs 找不到,有兩個解法:
- 從專案的 obj/Debug/80 中複製到指定的目錄下;
- Uno.UI 的版本到 1.41.1-dev.134;
- 無法處理 Android 鍵盤的 enter (done) 按鍵,所以 KeyDown event 會抓不到。
- 文章說 x:bind 可以用,但是我測試沒有反應,建議先使用 binding
- ListViewBase in Uno 跟 UWP 的差別,整合時需要多測試不相容的問題,例如:Header 不是所有的 XAML UIElement 都能顯示在 Android 或是 iOS 上。
- 記得修改 AssemblyInfo.cs 的 AssemblyProduct 與 AssemblyTitle 為對的名稱。
利用 KKBOX Open API 製作一個播放器來介紹使用 Uno Platform 開發有多快速。 使用畫面:
- Main Page: 讓用戶輸入關鍵字,藉由 KKBOX Open API 找到歌曲;
- Youtube Page:根據 Main Page 點擊的歌曲名稱,打 Youtube API 並搭配 WebView 播放内容;
MainPage.xaml.cs 的開發:
- 由於 KKBOX Open API 使用 .NET Standard 開發,所以從 Nuget 為 Android, iOS, UWP 加入參考;
- 申請 API 並建立 ViewModel 來處理關鍵字搜尋與顯示結果;
public ObservableCollection
Tracks { get; private set; } private KKBOXAPI apiClient; public MainPageViewModel() { apiClient = new KKBOXAPI(); Tracks = new ObservableCollection (); } public async Task InitAPI() { // 取得 Access Token var authResult = await KKBOXOAuth.SignInAsync(clientId, clientSecret); apiClient.AccessToken = authResult.Content.AccessToken; } public async Task SearchAsync() { // 搜尋歌曲,並加入 ObservableCollection 顯示在畫面上 var searchResult = await apiClient.SearchAsync(SearchKeyWord, 30, 0, SearchType.track); Tracks.Clear(); foreach (var item in searchResult.Content.Tracks.Data) { // 加入包裝好的 DataWrapper Tracks.Add(new TrackDataWrapper(item)); } OnPropertyChanged(nameof(Tracks)); } - 設計 MainPage.xaml 的顯示 XAML;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid Grid.Row="0" HorizontalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" Text="{Binding SearchKeyWord, Mode=TwoWay}"/> <Button Grid.Column="1" Click="OnSearchButtonClick"> <TextBlock Text="Search" /> </Button> </Grid> <ListView Grid.Row="1" ItemsSource="{Binding Tracks, Mode=OneWay}" IsItemClickEnabled="True" ItemClick="OnListViewItemClicked"> <ListView.ItemTemplate> <DataTemplate> <Grid HorizontalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Image Grid.Column="0" Source="{Binding AlbumUrl}" Width="60" Height="60" Margin="-10,0,0,0" /> <StackPanel Grid.Column="1"> <TextBlock Text="{Binding SongName}" Margin="10,0" FontSize="16" VerticalAlignment="Center"/> <TextBlock Text="{Binding ArtistWithAlbumName}" Margin="10,5,0,0" FontSize="14" VerticalAlignment="Center"/> </StackPanel> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <ProgressRing Grid.RowSpan="2" IsActive="True" Width="100" Height="100" Visibility="{Binding IsSearching, Converter={StaticResource BoolToVisibilityConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid>
畫面準備了 TextBox 與 Button,處理用戶輸入的内容來進行搜尋,並將結果 Binding 回到 ListView。接著點擊 ListViewItem 進入 YoutubePage.xaml。
YoutubePage.xaml.cs 的開發:
- 利用 YouTube Data API: Search 搜尋關鍵字,記得先申請 API key;
- 設計 YoutubePage.xaml 的顯示 XAML;
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <WebView Grid.Row="0" Source="{Binding PlayingVideoUrl, Mode=OneWay}" Height="{Binding WebPlayerHeight, Mode=OneWay}" Width="{Binding WebPlayerWidth, Mode=OneWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <ListView Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Videos, Mode=OneWay}" IsItemClickEnabled="true" ItemClick="OnListViewItemClicked"> <ListView.ItemTemplate> <DataTemplate> <Grid HorizontalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Image Grid.Column="0" Source="{Binding VideoImageUrl}" Width="60" Height="60" Margin="-10,0,0,0" /> <StackPanel Grid.Column="1"> <TextBlock Text="{Binding Name}" Margin="10,0" FontSize="16" VerticalAlignment="Center"/> </StackPanel> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <ProgressRing Grid.RowSpan="2" IsActive="True" Width="100" Height="100" Visibility="{Binding IsSearching, Converter={StaticResource BoolToVisibilityConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid>
- 處理 BackButton 的邏輯:
如果熟悉 UWP 可參考 Navigation history and backwards navigation for UWP apps:
public App() { Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested; } private void App_BackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e) { Frame rootFrame = Window.Current.Content as Frame; if (rootFrame.CanGoBack) { rootFrame.GoBack(); e.Handled = true; } }
- iOS 沒有實體按鈕可以處理 Back, 則設計 Back 按鈕讓用戶可以回到上一個畫面;
執行結果:
- UWP
- Android
- iOS
從上面的介紹裏面,是否發現一個重點:所有的 code 跟 xaml 都寫在 Shared 的專案裏面。
但是不同的平臺内容,依舊需在平臺專案下建立或是利用 define flag 來區隔,例如:以 Player 來説,UWP 使用 Windows.Media.Playback.MediaPlayer;Android 使用 Android.Media.MediaPlayer;iOS 使用 AVFoundation.AVPlayer;
我覺得這就是 Uno Platform 最容易入門的重點,很多處理都依賴熟悉的 .NET 與 UWP 開發技術,除非您需要使用到 Xamarin (或 Xamarin.Forms) 的内容才會需要。
[範例程式]
28-KKYoutubeUnoApp,使用時如果遇到 Android 因爲路徑過長無法編譯,請移動到 C:\ 下重新建立。
======
Uno Platform 確實讓習慣開發 UWP 的人更快入門。 它目前是 Preview 很多問題,所以 debug 或錯誤排除相對變困難許多,如果沒有一點 Xamarin (或 Xamarin.Forms) 的概念,要切換不同平臺觀念找到對應的 namespaces 需要花一點時間。
我期待它的發展,希望有幫助到找跨平臺開發 solution 的人有一些基本的觀念。
References:
- Uno Platform
- Uno Platform Solution Templates
- Uno Platform 偵查隊
- nventive/Uno.Windows-universal-samples
- Cross Platform Mobile Apps with .NET and Uno
- Introduction to WebAssembly for the Uno Platform (Part 1)