深入解讀.NET MAUI音樂播放器專案(三):介面互動

jevonsflash發表於2023-02-27

UI設計的本質是對於產品的理解在介面中多種形式的對映,當需求和定位不同時,對相同的功能表達出了不同的介面和互動方式。

作為播放器,介面可以是千差萬別的。《番茄播放器》的iOS平臺上我開發了傳統版本,和基於手勢播放的版本。

在這裡插入圖片描述
圖片來自於App Store宣傳圖

它們介面不同,但用的同一個播放核心。

作為播放核心專案,在MatoMusic.Core的工作已經結束。本系列博文重點還是在播放器思路的解讀,關於MAUI動畫互動,我打算有時間另外寫部落格(這裡給自己挖個坑)。 本專案中樸實無華的播放器介面部分,我想僅作為輔佐播放核心的示例,對於頁面和控制元件的Xaml部分不會展開描述。

在解決方案管理器中,我們新建MatoMusic專案,作為UI部分。

頁面

依賴包

在MatoMusic.csproj中新增對AbpAbp.AutoMapperAbp.Castle.Log4NetCommunityToolkit.Maui的包依賴

<ItemGroup>
  <PackageReference Include="Abp" Version="7.4.0" />
  <PackageReference Include="Abp.AutoMapper" Version="7.4.0" />
  <PackageReference Include="Abp.Castle.Log4Net" Version="7.4.0" />
  <PackageReference Include="CommunityToolkit.Maui" Version="2.0.0" />
</ItemGroup>

CommunityToolkit.Maui.Views.Popup為系統提供彈窗頁面支援

頁面設計

已註冊的路由頁面

  • NowPlayingPage - 正在播放頁面
  • QueuePage - 播放佇列頁面
  • MusicPage - 歌曲頁面
  • ArtistPage - 藝術家頁面
  • AlbumPage - 專輯頁面
  • PlaylistPage - 歌單頁面

以及導航或彈窗頁面

  • MusicCollectionPage - 歌曲集合詳情頁面
  • PlaylistEntryPage - 歌單詳情頁面
  • PlaylistFunctionPage - 歌單功能列表頁面
  • PlaylistChoosePage - 歌單選擇頁面

路由頁面可以從側滑選單欄或功能列表中透過指定的Uri跳轉

介面設計風格設計如下:

在這裡插入圖片描述

主頁面

.NET MAUI Shell 透過提供大多數應用所需的基本功能來降低應用開發的複雜性,應用視覺物件層次結構導航,詳情見官方文件

建立一個Shell頁面MainPage.cs作為初始介面:
在頁面Load完成後呼叫IMusicRelatedViewModel.InitAll()方法

public partial class MainPage : Shell, ITransientDependency
{
    private readonly IocManager iocManager;

    public MainPage(IocManager iocManager)
	{
		InitializeComponent();
        this.iocManager = iocManager;
        this.Init();
        Loaded += MainPage_Loaded;
    }

    private async void MainPage_Loaded(object sender, EventArgs e)
    {
        var musicRelatedViewModel = iocManager.Resolve<MusicRelatedService>();
        await musicRelatedViewModel.InitAll();
    }
}

在Xaml中定義各頁面的層次結構,隱式註冊的路由頁面:

<FlyoutItem Route="NowPlayingPage" Title="正在播放" Icon="tab_home.png">
    <ShellContent x:Name="NowPlayingPageShellContent"/>
</FlyoutItem>
<FlyoutItem Route="QueuePage" Title="播放佇列" Icon="tab_favorites.png">
    <ShellContent x:Name="QueuePageShellContent"/>
</FlyoutItem>
<FlyoutItem  Route="LibraryMainPage" Title="庫" Icon="tab_map.png" >
    <Tab>
        <ShellContent  Title="歌曲" Icon="headphone.png"  x:Name="MusicPageShellContent"/>
        <ShellContent  Title="藝術家" Icon="microphone2.png"  x:Name="ArtistPageShellContent"/>
        <ShellContent  Title="專輯" Icon="cd2.png"  x:Name="AlbumPageShellContent"/>
    </Tab>
</FlyoutItem>
<FlyoutItem  Route="PlaylistPage"  Title="歌單" Icon="tab_map.png">
    <ShellContent x:Name="PlaylistPageShellContent"/>
</FlyoutItem>

後端程式碼中為各ShellContent指定頁面物件

private void Init()
{
    var nowPlayingPage = iocManager.Resolve<NowPlayingPage>();
    var queuePage = iocManager.Resolve<QueuePage>();
    var playlistPage = iocManager.Resolve<PlaylistPage>();
    this.NowPlayingPageShellContent.Content = nowPlayingPage;
    this.QueuePageShellContent.Content = queuePage;
    this.PlaylistPageShellContent.Content = playlistPage;

    var musicPage = iocManager.Resolve<MusicPage>();
    var albumPage = iocManager.Resolve<AlbumPage>();
    var artistPage = iocManager.Resolve<ArtistPage>();

    this.MusicPageShellContent.Content = musicPage;
    this.ArtistPageShellContent.Content = artistPage;
    this.AlbumPageShellContent.Content = albumPage;
}

在App.xaml.cs中配置初始頁面

public partial class App : Application
{
    private readonly AbpBootstrapper _abpBootstrapper;

    public App(AbpBootstrapper abpBootstrapper)
    {
        _abpBootstrapper = abpBootstrapper;
        InitializeComponent();
        _abpBootstrapper.Initialize();
        this.MainPage = abpBootstrapper.IocManager.Resolve(typeof(MainPage)) as MainPage;
    }
}

基礎視覺化元素類

其中ContentPageContentViewPopup分別繼承於以下三個類別

ContentPageBase
ContentViewBase
PopupBase

他們包含Abp提供的本地化,物件對映,設定等服務,類圖如下

在這裡插入圖片描述

ContentPage和ContentViewBase包含曲目管理器IMusicInfoManager和播放控制服務IMusicControlService,類圖如下
在這裡插入圖片描述

導航

NavigationService,封裝了初始頁面的INavigation物件和導航方法

支援:

  • 路由方式的導航 - Shell 視覺層次結構中隱式註冊的路由。
  • 頁面導航 - 模式導航頁面可以從應用的任何位置推送到堆疊導航。

PushAsync或PushModalAsync可以按檔名的頁面導航

public async Task PushAsync(string pageName, object[] args = null)
 {
     var page = GetPageInstance(pageName, args);
     await mainPageNavigation.PushAsync(page);
 }
 
public async Task PushModalAsync(string pageName, object[] args = null)
{
    var page = GetPageInstance(pageName, args);
    await mainPageNavigation.PushModalAsync(page);
}

GetPageInstance透過反射的方式建立頁面物件
傳入物件名稱,引數和工具欄專案物件,返回頁面物件

private Page GetPageInstance(string obj, object[] args, IList<ToolbarItem> barItem = null)
{
    Page result = null;
    var namespacestr = "MatoMusic";
    Type pageType = Type.GetType(namespacestr + "." + obj, false);
    if (pageType != null)
    {
        try
        {
            var ctorInfo = pageType.GetConstructors()
                                  .Select(m => new
                                  {
                                      Method = m,
                                      Params = m.GetParameters(),
                                  }).Where(c => c.Params.Length == args.Length)
                                  .FirstOrDefault();
            if (ctorInfo==null)
            {
                throw new Exception("找不到對應的建構函式");
            }

            var argsDict = new Arguments();

            for (int i = 0; i < ctorInfo.Params.Length; i++)
            {
                var arg = ctorInfo.Params[i];
                argsDict.Add(arg.Name, args[i]);
            }

            var pageObj = iocManager.IocContainer.Resolve(pageType, argsDict) as Page;

            if (barItem != null && barItem.Count > 0)
            {
                foreach (var toolbarItem in barItem)
                {
                    pageObj.ToolbarItems.Add(toolbarItem);
                }
            }
            result = pageObj;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
        }
    }
    return result;
}

其中,彈窗的開啟和關閉由擴充套件類CommunityToolkit.Maui.Views.PopupExtensions提供方法

頁面資源

NET MAUI 單一專案使資原始檔可以儲存在統一位置上(一般是Resources資料夾下),為跨平臺方案使用。詳情見官方文件
將在Fonts新增FontAwesome字型檔案,以及Images中新增圖示png檔案
在這裡插入圖片描述
MatoMusic.csproj檔案中,對資源範圍進行限定,此時的限定範圍是Resources\Fonts\*Resources\Images\*

<ItemGroup>
	<!-- App Icon -->
	<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />

	<!-- Splash Screen -->
	<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />

	<!-- Images -->
	<MauiImage Include="Resources\Images\*" />
	<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />

	<!-- Custom Fonts -->
	<MauiFont Include="Resources\Fonts\*" />

	<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
	<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

樣式和主題

在移動端應用配色設計上,不同的應用應該有其獨特的設計風格,但因遵循配色原理。

基本配色要求有:應該保持主色統一,使主題鮮明突出;前景、背景色反差強烈使內容更容易閱讀;前景、背景有相應輔助色使介面靈動不古板。

因此係統樣式應該包含:

  • PhoneForegroundBrush - 前景色
  • PhoneContrastForegroundBrush - 輔前景色
  • PhoneBackgroundBrush - 背景色
  • PhoneContrastBackgroundBrush - 輔背景色
  • PhoneAccentBrush - 主色(亮色)
  • PhoneChromeBrush - 暗色

DarkTheme.xaml暗色主題配置

    <Color x:Key="PhoneBackgroundBrush">#181818</Color>
    <Color x:Key="PhoneForegroundBrush">White</Color>
    <Color x:Key="PhoneContrastBackgroundBrush">#222326</Color>
    <Color x:Key="PhoneContrastForegroundBrush">#DFD8F7</Color>
    <Color x:Key="PhoneAccentBrush">Teal</Color>
    <Color x:Key="PhoneChromeBrush">#A5A5A5</Color>

LightTheme.xaml亮色主題配置

    <Color x:Key="PhoneBackgroundBrush">White</Color>
    <Color x:Key="PhoneForegroundBrush">#181818</Color>
    <Color x:Key="PhoneContrastBackgroundBrush">#DFD8F7</Color>
    <Color x:Key="PhoneContrastForegroundBrush">#828386</Color>
    <Color x:Key="PhoneAccentBrush">Teal</Color>
    <Color x:Key="PhoneChromeBrush">#A5A5A5</Color>

CommonResourceDictionary.xaml中定義通用的控制元件樣式,如Label和Button控制元件,部分的定義如下

Label全域性樣式

<Style TargetType="Label">
    <Setter Property="TextColor" Value="{DynamicResource PhoneForegroundBrush}" />
    <Setter Property="FontFamily" Value="OpenSansRegular" />
</Style>

Button全域性樣式以及特定樣式


<Style TargetType="Button">
    <Setter Property="CornerRadius" Value="8"/>
    <Setter Property="TextColor" Value="{DynamicResource PhoneContrastForegroundBrush}" />
    <Setter Property="FontFamily" Value="OpenSansRegular" />
    <Setter Property="BackgroundColor" Value="{DynamicResource PhoneContrastBackgroundBrush}" />
    <Setter Property="Padding" Value="14,10" />
    <Setter Property="Margin" Value="5,0" />

</Style>
<Style TargetType="Button" x:Key="PrimaryButton">
    <Setter Property="CornerRadius" Value="8"/>
    <Setter Property="BackgroundColor" Value="Transparent"/>
    <Setter Property="TextColor" Value="{DynamicResource PhoneContrastForegroundBrush}" />
    <Setter Property="FontFamily" Value="OpenSansRegular" />
    <Setter Property="BackgroundColor" Value="{DynamicResource PhoneAccentBrush}" />
    <Setter Property="Padding" Value="14,10" />
    <Setter Property="Margin" Value="5,0" />
</Style>

<Style TargetType="Button" x:Key="TextButton">
    <Setter Property="BackgroundColor" Value="Transparent"/>
    <Setter Property="TextColor" Value="{DynamicResource PhoneContrastForegroundBrush}" />
    <Setter Property="FontFamily" Value="OpenSansRegular" />
    <Setter Property="BorderWidth" Value="0"/>
    <Setter Property="Padding" Value="14,10" />
    <Setter Property="Margin" Value="5,0" />
</Style>

<Style TargetType="Button" x:Key="PrimaryButtonOutline">
    <Setter Property="CornerRadius" Value="8"/>
    <Setter Property="BackgroundColor" Value="Transparent"/>
    <Setter Property="TextColor" Value="{DynamicResource PhoneContrastForegroundBrush}" />
    <Setter Property="FontFamily" Value="OpenSansRegular" />
    <Setter Property="BorderWidth" Value="1"/>
    <Setter Property="BorderColor" Value="{DynamicResource PhoneAccentBrush}"/>
    <Setter Property="Padding" Value="14,10" />
    <Setter Property="Margin" Value="5,0" />
</Style>

App.xaml中將主題和通用樣式囊括到資源字典中

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <style:DarkTheme />
            <!--<style:LightTheme />-->
            <style:CommonResourceDictionary />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

字型

配置
在MauiProgram.cs中,CreateMauiApp裡將FontAwesome字型加入配置

public static MauiApp CreateMauiApp()
{
	var builder = MauiApp.CreateBuilder();
	builder
		.UseMatoMusic<MatoMusicModule>()
		.UseMauiApp<App>()
		.UseMauiCommunityToolkit()
		.ConfigureFonts(fonts =>
		{
			fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
			fonts.AddFont("FontAwesome.ttf", "FontAwesome");
		});
	return builder.Build();
}

在Xaml中使用:在帶有文字屬性的標籤中,如<Button><Label>,設定FontFamily屬性為FontAwesome,設定Text屬性為FontAwesome字元內容,此值可使用“字元對映表”工具查詢。

<Button Text="" FontFamily="FontAwesome"></Button>

在本地計算機中安裝好FontAwesome字型後,開啟“字元對映表”工具,選擇字型FontAwesome,點選後可以從下面的輸入框中複製內容
在這裡插入圖片描述
在這裡插入圖片描述

本地化

使用Abp提供的本地化方案

在MatoMusic.Core專案的MatoMusicCoreModule.cs中

public class MatoMusicCoreModule : AbpModule
{
	public override void PreInitialize()
	{
		LocalizationConfigurer.Configure(Configuration.Localization);
	}
    ...
}
      

Localization/MatoMusicLocalization.cs中,將提供基於Xml的本地化的語言字典配置,從MatoMusic.Core.Localization.SourceFiles資源中訪問字典:

public static void Configure(ILocalizationConfiguration localizationConfiguration)
{
    localizationConfiguration.Sources.Add(
        new DictionaryBasedLocalizationSource(MatoMusicConsts.LocalizationSourceName,
            new XmlEmbeddedFileLocalizationDictionaryProvider(
                typeof(LocalizationConfigurer).GetAssembly(),
                "MatoMusic.Core.Localization.SourceFiles"
            )
        )
    );
}

在這些檔案的編譯模式應為嵌入的資源
在這裡插入圖片描述
基礎視覺化元素類中提供L方法,返回本地化字串

protected virtual string L(string name)
{
    return LocalizationSource.GetString(name);
}

TranslateExtension實現IMarkupExtension,MarkupLanguage的本質,是例項化一個物件。Xaml編譯器,會呼叫標記擴充套件物件的ProvideValue方法,並將返回值賦值給使用了標記擴充套件的屬性,ProvideValue中呼叫L方法完成翻譯

[ContentProperty("Text")]
public class TranslateExtension : DomainService, IMarkupExtension
{
    public TranslateExtension()
    {
        LocalizationSourceName = MatoMusicConsts.LocalizationSourceName;

    }
    public string Text { get; set; }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
        Console.WriteLine(CultureInfo.CurrentUICulture);
        if (Text == null)
            return "";
        var translation = L(Text);
        return translation;
    }
}

在Xaml中使用:在帶有文字屬性的標籤中,如<Button><Label>,Text屬性的值將轉換為本地化字串值。

ViewModel

Model-View-ViewModel (MVVM) 設計模式是在稱為檢視的 Xaml 使用者介面和基礎資料(稱為模型)之間的一個軟體層,稱之為檢視模型,即ViewModel,

介面檢視和ViewModel透過Xaml中定義的資料繫結進行連線。 在檢視類的建構函式中,我們以注入的方式將ViewModel匯入檢視,並賦值給BindingContext屬性,例如在NowPlayingPage.cs中:

public NowPlayingPage(NowPlayingPageViewModel nowPlayingPageViewModel)
{
	InitializeComponent();
	this.BindingContext = nowPlayingPageViewModel;
	...
}

音樂相關服務類

前一章介紹了播放核心的兩個類曲目管理器IMusicInfoManager和播放控制服務IMusicControlService,介面互動物件中對這兩個類進行了應用。

MusicRelatedService是播放控制服務的一層封裝,它基於ViewModelBase。

抽象的來說,音樂相關服MusicRelatedService包含一系列可繫結的屬性,自動維護屬性值,並在設定屬性值時呼叫放控制服務完成業務變更操作。資料作為介面互動的支撐。

主要屬性:

  • NextMusic - 下一首曲目
  • PreviewMusic - 上一首曲目

NextMusic和PreviewMusic用於繫結首頁的上一曲、下一曲專輯封面。

  • CurrentMusic - 當前曲目:正在播放的曲目,所有的音樂相關操作的物件都是當前曲目CurrentMusic。

CurrentMusic可在介面提供雙向繫結支援,當其值變更時,代表切換播放歌曲。

  1. 呼叫IMmusicControlService.InitPlayer,設定播放器曲目
  2. 更新當前曲目的長度
  3. 更新上一首、下一首曲目
  4. 更新BreakPointMusicIndex值,並用SettingManager持久化當前曲目的角標編號

程式碼實現如下:

if (e.PropertyName == nameof(CurrentMusic))
{
    if (!Canplay || IsInited == false)
    {
        return;

    }
    await musicControlService.InitPlayer(CurrentMusic);
    DoUpdate();
    InitPreviewAndNextMusic();
    Duration = GetPlatformSpecificTime(musicControlService.Duration());
    SettingManager.ChangeSettingForApplication(CommonSettingNames.BreakPointMusicIndex, Musics.IndexOf(CurrentMusic).ToString());

}
  • Musics - 當前播放佇列:可供播放的有序曲目集合,是自然播放、上一曲、下一曲、隨機播放的範圍。

  • Canplay - 表明當前曲目是否可供播放

實現如下:

public bool Canplay => this.CurrentMusic != null;
  • CanplayAll - 指示是否可以播放全部曲目,噹噹前播放佇列為空時,介面將顯示嚮導

實現如下:

public bool CanplayAll => Musics.Count > 0;
  • IsPlaying 表明是否正在播放

它的值變更由IMusicControlService.OnPlayStatusChanged事件觸發,以實現自動維護屬性值:

musicControlService.OnPlayStatusChanged+=MusicControlService_OnPlayStatusChanged;

private void MusicControlService_OnPlayStatusChanged(object sender, bool e)
{
    this.IsPlaying = e;
}
  • Duration - 指示當前曲目時長
  • CurrentTime - 指示當前曲目播放進度

這兩個屬性由一個自動定時器,每隔一段時間自動觸發DoUpdate方法,以實現自動維護屬性值:

public bool DoUpdate()
{
    this.CurrentTime = GetPlatformSpecificTime(musicControlService.CurrentTime());
    this.Duration = GetPlatformSpecificTime(musicControlService.Duration());

    return true;
}
  • IsShuffle - 指示隨機播放模式,是否為隨機播放
  • IsRepeatOne - 指示單曲迴圈模式,是否為單曲迴圈

這兩個屬性由SettingManager持久化其值。

  • IsInited - 指示是否完成初始化服務

主要方法:

  • InitCurrentMusic - 初始化當前曲目CurrentMusic

根據當前曲目的角標編號BreakPointMusicIndex值,從曲目庫中獲取當前曲目物件,並賦值給CurrentMusic。

  • InitAll - 初始化服務,用於系統啟動後的一次性呼叫

呼叫IMusicControlService.RebuildMusicInfos從播放列隊中讀取音訊列表,完成後觸發OnBuildMusicInfosFinished事件,觸發InitCurrentMusic。

音樂相關ViewModel類

MusicRelatedViewModel是一個抽象類,基於ViewModelBase,包含MusicRelatedService,以及曲目管理器IMusicInfoManager和播放控制服務IMusicControlService物件,其子類可直接用於介面UI元素的繫結。

MusicRelatedViewModel的子類中,MusicRelatedService物件不需要在建構函式中注入,它將在訪問器中初始化。

public MusicRelatedService MusicRelatedService
{
    get
    {
        if (_musicRelatedService==null)
        {
            _musicRelatedService = IocManager.Instance.Resolve<MusicRelatedService>();
            _musicRelatedService.PropertyChanged += this.Delegate_PropertyChanged;
        }
        return _musicRelatedService;
    }

}

並且在MusicRelatedViewModel的子類中,實現了對MusicRelatedService物件屬性變更的事件冒泡:

private void Delegate_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    this.RaisePropertyChanged(e.PropertyName);
}

主要屬性:

  • NextMusic - 下一首曲目

  • PreviewMusic - 上一首曲目

  • CurrentMusic - 當前曲目:正在播放的曲目,所有的音樂相關操作的物件都是當前曲目CurrentMusic。

  • Musics - 當前播放佇列:可供播放的有序曲目集合,是自然播放、上一曲、下一曲、隨機播放的範圍。

  • Canplay - 表明當前曲目是否可供播放

  • CanplayAll - 指示是否可以播放全部曲目,噹噹前播放佇列為空時,介面將顯示嚮導

  • IsPlaying 表明是否正在播放

  • Duration - 指示當前曲目時長

  • CurrentTime - 指示當前曲目播放進度

  • IsShuffle - 指示隨機播放模式,是否為隨機播放

  • IsRepeatOne - 指示單曲迴圈模式,是否為單曲迴圈

  • PlayCommand - 播放/暫停命令

  • PreCommand - 上一曲命令

  • NextCommand - 下一曲命令

  • ShuffleCommand - 切換隨機模式命令

  • RepeatOneCommand - 切換單曲迴圈命令

  • FavouriteCommand - 設定/取消設定“我最喜愛”

資料繫結

在NowPlayingPage中,對當前播放的曲目,以及上一首、下一首的曲目資訊進行顯示。
對於當前曲目的長度和進度,進行顯示和控制,對播放的曲目進行控制等內容:

<Grid Grid.Column="1">
    <Image HorizontalOptions="Fill"
           x:Name="PreAlbumArt"
           HeightRequest="{Binding Source={x:Reference Name=RefBox}, Path=Height}"
           WidthRequest="{Binding Source={x:Reference Name=RefBox}, Path=Width}"
           VerticalOptions="Fill"
           TranslationX="-320"
           Source="{Binding PreviewMusic.AlbumArt,Converter={StaticResource AlbumArtConverter}}">
    </Image>

    <Image 
        HeightRequest="{Binding Source={x:Reference Name=RefBox}, Path=Height}"
        WidthRequest="{Binding Source={x:Reference Name=RefBox}, Path=Width}"
        HorizontalOptions="Fill"
        VerticalOptions="Fill"
        Source="{Binding CurrentMusic.AlbumArt,Converter={StaticResource AlbumArtConverter}}">
        <Image.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding SwitchPannelCommand}"></TapGestureRecognizer>
        </Image.GestureRecognizers>
    </Image>

    <Image 
         x:Name="NextAlbumArt"
         HeightRequest="{Binding Source={x:Reference Name=RefBox}, Path=Height}"
         WidthRequest="{Binding Source={x:Reference Name=RefBox}, Path=Width}"
         HorizontalOptions="Fill"
         VerticalOptions="Fill"
         TranslationX="320"                           
         Source="{Binding NextMusic.AlbumArt,Converter={StaticResource AlbumArtConverter}}">
    </Image>
</Grid>

對曲目名稱,藝術家的繫結:

 <StackLayout Grid.Column="1" HorizontalOptions="Center">
     <Label Text="{Binding CurrentMusic.Title}" 
                    HorizontalOptions="FillAndExpand" 
                    HorizontalTextAlignment="Center" 
                    FontSize="{StaticResource BodyFontSize}" 
                    TextColor="White" />
     <Label Margin="0,-5,0,0" 
                    Text="{Binding CurrentMusic.Artist}" 
                LineBreakMode="TailTruncation"
                    HorizontalOptions="FillAndExpand" 
                    HorizontalTextAlignment="Center" 
                    FontSize="{StaticResource BodyFontSize}" 
                    TextColor="{DynamicResource PhoneChromeBrush}" />
 </StackLayout>

介面效果如下:
在這裡插入圖片描述
小窗播放控制元件MusicMiniView也對曲目資訊進行了相似的繫結
在這裡插入圖片描述
進度控制區域程式碼:

<!--進度控制區域-->
<Grid Grid.Row="2"
              x:Name="ProgressControlLayout"
              BindingContext="{Binding}">

    <StackLayout Margin="0,0,0,0" Orientation="Horizontal">
        <Label Text="{Binding CurrentTime,Converter={StaticResource SecondsToTimeSpanConverter}}" 
                       TextColor="{DynamicResource PhoneChromeBrush}" 
                       FontSize="{StaticResource TinyFontSize}" 
                       HorizontalOptions="StartAndExpand" />
        <Label  Text="{Binding Duration,Converter={StaticResource SecondsToTimeSpanConverter}}" 
                        TextColor="{DynamicResource PhoneChromeBrush}" 
                        FontSize="{StaticResource TinyFontSize}" 
                        HorizontalOptions="End" />
    </StackLayout>
    <Slider                       
        Maximum="{Binding Duration,Converter={StaticResource SliderMaxValueConverter}}"
        Minimum="0.0"              
        MinimumTrackColor="{DynamicResource PhoneAccentBrush}"
        IsEnabled="{Binding Canplay}"
        ValueChanged="OnValueChanged"
        Value="{Binding CurrentTime,Mode=TwoWay} ">
    </Slider>
</Grid>

播放控制區域程式碼:

<!--播放控制按鈕-->
<Grid
    Grid.Row="3"
    BindingContext="{Binding}"
    x:Name="PlayControlLayout">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"></ColumnDefinition>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        <ColumnDefinition Width="1*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Button VerticalOptions="Center"
            HorizontalOptions="StartAndExpand"
            Grid.Column="0"
            Command="{Binding ShuffleCommand}" 
            FontFamily="FontAwesome"
            FontSize="{StaticResource BodyFontSize}"
            Text="{Binding IsShuffle,Converter={StaticResource Bool2StringConverter},ConverterParameter=|}"/>
    <Grid Grid.Column="1"  HorizontalOptions="Center" WidthRequest="216">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"></ColumnDefinition>
            <ColumnDefinition Width="2*"></ColumnDefinition>
            <ColumnDefinition Width="1*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Button 
            
            VerticalOptions="Center" 
            Grid.Column="0"
            Command="{Binding PreCommand}" 
            FontFamily="FontAwesome"
            FontSize="{StaticResource BodyFontSize}"
            Text=""
            />
        <Button 
            VerticalOptions="Center"  
            Grid.Column="1"
            Command="{Binding PlayCommand}" 
            Style="{StaticResource PrimaryButton}"

            FontFamily="FontAwesome"
            FontSize="{StaticResource StandOutBodyFontSize}"
            Text="{Binding IsPlaying,Converter={StaticResource Bool2StringConverter},ConverterParameter=|} "/>

        <Button 
            VerticalOptions="Center"       
            Grid.Column="2"
            Command="{Binding NextCommand}"
            FontFamily="FontAwesome"
            FontSize="{StaticResource BodyFontSize}"
            Text=""/>
    </Grid>
    <Button VerticalOptions="Center" 
                    HorizontalOptions="EndAndExpand"
                    Grid.Column="2"
                    Command="{Binding RepeatOneCommand}" 
                    FontFamily="FontAwesome"
                    FontSize="{StaticResource BodyFontSize}"
                    Text="{Binding IsRepeatOne,Converter={StaticResource Bool2StringConverter},ConverterParameter=|}" />
</Grid>

列表分組顯示

下列三個頁面使用ListView控制元件呈現曲目,專輯,藝術家的可滾動垂直列表

  • MusicPage - 歌曲頁面
  • ArtistPage - 藝術家頁面
  • AlbumPage - 專輯頁面

透過將 設定 ListView.GroupHeaderTemplateDataTemplate來自定義每個組標頭的外觀

在MusicGroupHeaderView.xaml中,定義分組標頭的外觀,由一個標題和亮色方形背景組成

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MatoMusic.MusicGroupHeaderView">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto">
            </ColumnDefinition>
            <ColumnDefinition>
            </ColumnDefinition>
        </Grid.ColumnDefinitions>
        <BoxView Color="{DynamicResource PhoneAccentBrush}"
                     Margin="0,5"
                     WidthRequest="54">
        </BoxView>
        <Label VerticalTextAlignment="Center"
               HorizontalTextAlignment="Center"
               Text="{Binding Title}"
               FontAttributes="Bold"
               TextColor="{DynamicResource PhoneForegroundBrush}"
               FontSize="{StaticResource BodyFontSize}">
        </Label>
    </Grid>

</ContentView>

在頁面控制元件中,IsGroupingEnabled設定為true,並指定GroupHeaderTemplate屬性為MusicGroupHeaderView

<ListView
    IsGroupingEnabled="true"
    <ListView.GroupHeaderTemplate>
        <DataTemplate>
            <ViewCell>
                <mato:MusicGroupHeaderView></mato:MusicGroupHeaderView>
            </ViewCell>
        </DataTemplate>
    </ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>

介面效果如下
在這裡插入圖片描述

專案地址

GitHub:MatoMusic

相關文章