UI設計的本質是對於產品的理解在介面中多種形式的對映,當需求和定位不同時,對相同的功能表達出了不同的介面和互動方式。
作為播放器,介面可以是千差萬別的。《番茄播放器》的iOS平臺上我開發了傳統版本,和基於手勢播放的版本。
圖片來自於App Store宣傳圖
它們介面不同,但用的同一個播放核心。
作為播放核心專案,在MatoMusic.Core的工作已經結束。本系列博文重點還是在播放器思路的解讀,關於MAUI動畫互動,我打算有時間另外寫部落格(這裡給自己挖個坑)。 本專案中樸實無華的播放器介面部分,我想僅作為輔佐播放核心的示例,對於頁面和控制元件的Xaml部分不會展開描述。
在解決方案管理器中,我們新建MatoMusic專案,作為UI部分。
頁面
依賴包
在MatoMusic.csproj中新增對Abp
,Abp.AutoMapper
,Abp.Castle.Log4Net
,CommunityToolkit.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;
}
}
基礎視覺化元素類
其中ContentPage
,ContentView
,Popup
分別繼承於以下三個類別
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可在介面提供雙向繫結支援,當其值變更時,代表切換播放歌曲。
- 呼叫IMmusicControlService.InitPlayer,設定播放器曲目
- 更新當前曲目的長度
- 更新上一首、下一首曲目
- 更新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>
介面效果如下