淺談 Uno Platform 開發

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 找不到,有兩個解法:
    1. 從專案的 obj/Debug/80 中複製到指定的目錄下;
    2. 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 的開發:

  1. 由於 KKBOX Open API 使用 .NET Standard 開發,所以從 Nuget 為 Android, iOS, UWP 加入參考;
  2. 申請 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));
    }
  3. 設計 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>

畫面準備了 TextBoxButton,處理用戶輸入的内容來進行搜尋,並將結果 Binding 回到 ListView。接著點擊 ListViewItem 進入 YoutubePage.xaml。

YoutubePage.xaml.cs 的開發

  1. 利用 YouTube Data API: Search 搜尋關鍵字,記得先申請 API key
  2. 設計 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>
    藉由 WebView 顯示 Youtube 内容,如果您遇到顯示 Youtube 的問題,可以參考 UWP - Add Youtube video in the WebView的介紹;
  3. 處理 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;
        }
    }
    
  4. 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