@
我們將繪製一個圓形的音樂播放控制元件,它包含一個圓形的進度條、專輯頁面和播放按鈕。
關於圖形繪製
使用MAUI的繪製功能,需要Microsoft.Maui.Graphics庫。
Microsoft.Maui.Graphics 是一個實驗性的跨平臺圖形庫,它可以在 .NET MAUI 中使用。它提供了一組基本的圖形元素,如矩形、圓形、線條、路徑、文字和影像。它還提供了一組基本的圖形操作,如填充、描邊、裁剪、變換和漸變。
Microsoft.Maui.Graphics在不同的目標平臺上使用一致的API訪問本機圖形功能,而底層實現使用了不同的圖形渲染引擎。其中通用性較好的是SkiaSharp圖形庫,支援幾乎所有的作業系統,在不同平臺上的表現也近乎一致。
建立自定義控制元件
在專案中新增SkiaSharp繪製功能的引用Microsoft.Maui.Graphics.Skia
以及SkiaSharp.Views.Maui.Controls
。
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.59" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.3" />
</ItemGroup>
建立CircleSlider.xaml檔案,新增如下內容:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
x:Class="MatoMusic.Controls.CircleSlider">
<ContentView.Content>
<forms:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
</ContentView.Content>
</ContentView>
SKCanvasView是SkiaSharp.Views.Maui.Controls封裝的View控制元件。
開啟CircleSlider.xaml.cs檔案
控制元件將包含以下可繫結屬性:
- Maximum:最大值
- Minimum:最小值
- Value:當前值
- TintColor:進度條顏色
- ContainerColor:進度條背景顏色
- BorderWidth:進度條寬度
定義兩個SKPaint畫筆屬性,OutlinePaint用於繪製進度條背景,ArcPaint用於繪製進度條本身。他們的描邊寬度StrokeWidth則是圓形進度條的寬度。
兩個畫筆的初始值樣式為SKPaintStyle.Stroke,描邊寬度為BorderWidth的值。
private SKPaint _outlinePaint;
public SKPaint OutlinePaint
{
get
{
if (_outlinePaint == null)
{
SKPaint outlinePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = BorderWidth,
};
_outlinePaint = outlinePaint;
}
return
_outlinePaint;
}
set { _outlinePaint = value; }
}
private SKPaint _arcPaint;
public SKPaint ArcPaint
{
get
{
if (_arcPaint == null)
{
SKPaint arcPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = BorderWidth,
};
_arcPaint = arcPaint;
}
return _arcPaint;
}
set { _arcPaint = value; }
}
SetStrokeWidth用於設定描邊寬度,併產生一個動效,
在BorderWidth發生變更的時候,會出現一個動效。寬度會緩慢地變化至新的值。重新整理率為10ms一次,每次變化的值為1。
private float _borderWidth;
public float BorderWidth
{
get { return _borderWidth; }
set
{
var old_borderWidth = _borderWidth;
var span = value - old_borderWidth;
SetStrokeWidth(span, old_borderWidth);
_borderWidth = value;
this.ArcPaint.StrokeWidth = _borderWidth;
this.OutlinePaint.StrokeWidth = _borderWidth;
}
}
private async void SetStrokeWidth(float span, float old_borderWidth)
{
if (span > 0)
{
for (int i = 0; i <= span; i++)
{
await Task.Delay(10);
this.ArcPaint.StrokeWidth = old_borderWidth + i;
this.OutlinePaint.StrokeWidth = old_borderWidth + i;
RefreshMainRectPadding();
}
}
else
{
for (int i = 0; i >= span; i--)
{
await Task.Delay(10);
this.ArcPaint.StrokeWidth = old_borderWidth + i;
this.OutlinePaint.StrokeWidth = old_borderWidth + i;
RefreshMainRectPadding();
}
}
}
於此同時,因為描邊寬度變化了,需要對Padding進行補償。呼叫RefreshMainRectPadding方法計算一個新的Padding值,BoderWidth縮小時,Padding也隨之增大。
private void RefreshMainRectPadding()
{
this._mainRectPadding = this.BorderWidth / 2;
}
在視覺上,進度條寬度從內向外擴張變細。
若設為原寬度減去計算值,從視覺上是從外向內收縮變細。
private void RefreshMainRectPadding()
{
this._mainRectPadding = 15 - this.BorderWidth / 2;
}
接下來寫訂閱了CanvaseView的PaintSurface事件的方法OnCanvasViewPaintSurface。在這個方法中,我們將編寫圓形進度條的繪製邏輯。
PaintSurface事件在繪製圖形時觸發。程式執行時會實時觸發這個方法,它的引數SKPaintSurfaceEventArgs事件附帶的物件具有兩個屬性:
- Info型別SKImageInfo
- Surface型別SKSurface
SKImageInfo物件包含如寬度和高度等有關繪圖區域的資訊,物件SKSurface為繪製本身,我們需要利用SKImageInfo寬度和高度等資訊,結合業務資料,在SKSurface繪製出我們想要的圖形。
清空上一次繪製的圖形,呼叫SKSurface.Canvas獲取Canvas物件,呼叫Canvas.Clear方法清空上一次繪製的圖形。
canvas.Clear();
rect是一個SKRect物件,進度條本身是圓形,我們需要一個正方形的區域來控制圓形區域。
sweepAngle是當前進度對應的角度,首先計算出總進度值,透過計算當前進度對應總進度的比值,換算成角度,將這一角度賦值給sweepAngle。
startAngle是進度條的起始角度,我們將其設定為-90度,即從正上方開始繪製。
SKRect rect = new SKRect(_mainRectPadding, _mainRectPadding, info.Width - _mainRectPadding, info.Height - _mainRectPadding);
float startAngle = -90;
float sweepAngle = (float)((Value / SumValue) * 360);
呼叫Canvas.DrawOval,使用OutlinePaint畫筆繪製進度條背景,它是一個圓形
canvas.DrawOval(rect, OutlinePaint);
建立繪製路徑path,呼叫AddArc方法,將rect物件和起始角度和終止角度傳入,即可繪製出弧形。
using (SKPath path = new SKPath())
{
path.AddArc(rect, startAngle, sweepAngle);
canvas.DrawPath(path, ArcPaint);
}
繪製部分的完整程式碼如下:
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
var SumValue = Maximum - Minimum;
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKRect rect = new SKRect(_mainRectPadding, _mainRectPadding, info.Width - _mainRectPadding, info.Height - _mainRectPadding);
float startAngle = -90;
float sweepAngle = (float)((Value / SumValue) * 360);
canvas.DrawOval(rect, OutlinePaint);
using (SKPath path = new SKPath())
{
path.AddArc(rect, startAngle, sweepAngle);
canvas.DrawPath(path, ArcPaint);
}
}
使用控制元件
在MainPage.xaml中新增一個CircleSlider控制元件,
設定的Maximum,是當前曲目的時長,Value是當前曲目的進度
<controls:CircleSlider
HeightRequest="250"
WidthRequest="250"
x:Name="MainCircleSlider"
Maximum="{Binding Duration}"
Minimum="0.0"
TintColor="#FFFFFF"
ContainerColor="#4CFFFFFF"
IsEnabled="{Binding Canplay}"
ValueChanged="OnValueChanged"
Value="{Binding CurrentTime,Mode=TwoWay} ">
</controls:CircleSlider>
建立專輯封面
使用MAUI的VisualElement中的Clip屬性,建立Clip裁剪,可以傳入一個Geometry物件,這裡我們使用RoundRectangleGeometry,將它的CornerRadius屬性設定為圖片寬度的一半,即可實現圓形圖片。
<Image HeightRequest="250"
WidthRequest="250"
Margin="7.5"
Source="{Binding CurrentMusic.AlbumArt}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill">
<Image.Clip>
<RoundRectangleGeometry CornerRadius="125" Rect="0,0,250,250" />
</Image.Clip>
</Image>
設定一個半透明背景的播放狀態指示器,當IsPlaying為False時將顯示一個播放按鈕
<Grid IsVisible="{Binding IsPlaying, Converter={StaticResource True2FalseConverter}}">
<BoxView HeightRequest="250"
WidthRequest="250"
Margin="7.5"
Color="#60000000"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
CornerRadius="250" ></BoxView>
<Label x:Name="PauseLabel"
HorizontalOptions="CenterAndExpand"
FontSize="58"
TextColor="{Binding Canplay,Converter={StaticResource Bool2StringConverter},ConverterParameter=White|#434343}"
FontFamily="FontAwesome"
Margin="0"></Label>
</Grid>
建立PanContainer物件,用於實現拖動效果,設定AutoAdsorption屬性為True,即可實現拖動後自動吸附效果。
關於PanContainer請檢視上期的文章:平移手勢互動
用一個Grid將專輯封面,CircleSlider,以及播放狀態指示器包裹起來。完整程式碼如下
<controls1:PanContainer BackgroundColor="Transparent"
x:Name="DefaultPanContainer"
OnTapped="DefaultPanContainer_OnOnTapped"
AutoAdsorption="True"
OnfinishedChoise="DefaultPanContainer_OnOnfinishedChoise">
<Grid PropertyChanged="BindableObject_OnPropertyChanged"
VerticalOptions="Start"
HorizontalOptions="Start">
<Image HeightRequest="250"
WidthRequest="250"
Margin="7.5"
Source="{Binding CurrentMusic.AlbumArt}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill">
<Image.Clip>
<RoundRectangleGeometry CornerRadius="125" Rect="0,0,250,250" />
</Image.Clip>
</Image>
<controls:CircleSlider>...</controls:CircleSlider>
<Grid IsVisible="{Binding IsPlaying, Converter={StaticResource True2FalseConverter}}">
<BoxView HeightRequest="250"
WidthRequest="250"
Margin="7.5"
Color="#60000000"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
CornerRadius="250" ></BoxView>
<Label x:Name="PauseLabel"
HorizontalOptions="CenterAndExpand"
FontSize="58"
TextColor="{Binding Canplay,Converter={StaticResource Bool2StringConverter},ConverterParameter=White|#434343}"
FontFamily="FontAwesome"
Margin="0"></Label>
</Grid>
</Grid>
</controls1:PanContainer>
以上就是這個專案的全部內容,感謝閱讀