在網路上看到了篇關於 SkiaSharp 套件繪製 SVG 圖形的文章 -- Xamarin SkiaSharp: using svg ,心中冒出一個想法,那我能不能利用這個套件做一個類似 Button 的控制項,而且上頭的圖形是採用 SVG 圖檔的呢?
首先依照參考文章加入相關的 nuget 套件 (1) SkiaSharp.Views.Foms ( 這個套件同時會幫你安裝 SkiaSharp) (2) SkiaSharp.Svg。
建立一個繼承自 SkiaSharp.Views.Forms 中的 SKCanvasView Class 的自訂類別 SvgControl,我們需要覆寫 SkCanvasView 的 OnPaintSurface method,使得它會在 SkCanvas 上繪圖。
public class SvgControl : SKCanvasView
{
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var svg = CreateSKSvg();
using (SKPaint paint = new SKPaint())
{
e.Surface.Canvas.Clear();
e.Surface.Canvas.DrawPicture(svg.Picture, paint);
}
}
private SkiaSharp.Extended.Svg.SKSvg CreateSKSvg()
{
string svgText = "<?xml version='1.0' encoding='utf-8'?>" +
"<svg xmlns='http://www.w3.org/2000/svg' height='128' width='128' viewBox='0 0 128 128'>" +
"<g>" +
"<path id='path1' transform='rotate(0,64,64) translate(26.4105767058103,0) scale(3.9988749808127,3.9988749808127) ' Fill='#FFFFFF' " +
"d='M16.599986,4.4019985L3.0000018,17.004999 16.599986,27.808001z M18.8,0L18.599986,32.007996 18.599986,32.009003 0,17.205z' />" +
"</g>" +
"</svg>";
byte[] bytes = Encoding.UTF8.GetBytes(svgText);
MemoryStream stream = new MemoryStream(bytes);
var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(stream);
return svg;
}
}
執行結果如下,看起來沒有太理想,SVG 不是太聽話,維持著自己的尺寸:
現在出現了一個麻煩的問題,總不能為了尺寸問題要去改 SVG 的內容吧?下一步來建立可以依照外部大小縮放的程式碼,參照 Xamarin Forms 的 Aspect 列舉,讓這個控制項能夠依賴自身 Aspect 屬性的設定做不同的比例縮放。
加入 Aspect property 以及相對應的 BindableProperty。
public static readonly BindableProperty AspectProperty =
BindableProperty.Create("Aspect", typeof(Aspect), typeof(SvgControl), Aspect.AspectFit);
public Aspect Aspect
{
get { return (Aspect)GetValue(AspectProperty); }
set { SetValue(AspectProperty, value); }
}
囉嗦地解釋一下 Aspect 列舉:
(1) Aspect.AspectFit:表示以容器的短邊作為縮放標準,也就是圖形會完整地呈現在容器內,而且不會變形。
(2) Aspect.AspectFill:表示以容器的長邊作為縮放標準,圖形會充滿容器且不會變形,但有可能會有多餘的部分被裁切。
(3) Aspect.Fill:表示以容器相對高寬個別比例做為縮放標準,圖形充滿容器、不會裁切,但有可能變形。
在進行計算之前,先建立一個資料結構來存放計算所需的基準資料。
internal class RatioRange
{
/// <summary>
/// 容器比較長的那一邊
/// </summary>
public float Max { get; set; }
/// <summary>
/// 容器比較短的那一邊
/// </summary>
public float Min { get; set; }
/// <summary>
/// 容器原來的寬
/// </summary>
public float Width { get; set; }
/// <summary>
/// 容器原來的高
/// </summary>
public float Height { get; set; }
}
加入計算比例的程式碼。
private float GetWidthScaleRatio(SKImageInfo info, SkiaSharp.Extended.Svg.SKSvg svg)
{
return info.Width / svg.CanvasSize.Width;
}
private float GetHeightScaleRatio(SKImageInfo info, SkiaSharp.Extended.Svg.SKSvg svg)
{
return info.Height / svg.CanvasSize.Height;
}
private RatioRange GetRatioRange(SKImageInfo info, SkiaSharp.Extended.Svg.SKSvg svg)
{
var ratioRange = new RatioRange();
ratioRange.Width = GetWidthScaleRatio(info, svg);
ratioRange.Height = GetHeightScaleRatio(info, svg);
if (ratioRange.Width > ratioRange.Height)
{
ratioRange.Max = ratioRange.Width;
ratioRange.Min = ratioRange.Height;
}
else
{
ratioRange.Max = ratioRange.Height;
ratioRange.Min = ratioRange.Width;
}
return ratioRange;
}
再加入決定如何縮放的部分。
private void ScaleCanvas(SKPaintSurfaceEventArgs e, SkiaSharp.Extended.Svg.SKSvg svg)
{
var rationRange = GetRatioRange(e.Info, svg);
switch (Aspect)
{
case Aspect.Fill:
e.Surface.Canvas.Scale(rationRange.Width, rationRange.Height);
break;
case Aspect.AspectFill:
e.Surface.Canvas.Scale(rationRange.Max);
break;
default:
e.Surface.Canvas.Scale(rationRange.Min);
break;
}
}
修改 OnPaintSurface method。
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var svg = CreateSKSvg();
ScaleCanvas(e, svg);
using (SKPaint paint = new SKPaint())
{
e.Surface.Canvas.Clear();
e.Surface.Canvas.DrawPicture(svg.Picture, paint);
}
}
先來看看目前的成果,是不是棒呆了?
還剩下最後一個工作,讓 SVG 檔案能夠以內嵌資源的形式儲存,並且透過屬性傳進來。在此先增加一個 EmbeddedResource property 與相對應的 BindableProperty。
public static readonly BindableProperty EmbeddedResourceProperty =
BindableProperty.Create("EmbeddedResource", typeof(string), typeof(SvgControl), default(string));
public string EmbeddedResource
{
get { return (string)GetValue(EmbeddedResourceProperty); }
set { SetValue(EmbeddedResourceProperty, value); }
}
加上取得內嵌資源的程式碼。
private Stream GetEmbeddedResourceStream()
{
var assembly = this.GetType().GetTypeInfo().Assembly;
if (assembly.GetManifestResourceNames().Any((x) => x == EmbeddedResource))
{
return assembly.GetManifestResourceStream(EmbeddedResource);
}
else
{
throw new Exception(string.Format("Embedde resource {0} not found.", EmbeddedResource));
}
}
修改 CreateSkSvg method。
private SkiaSharp.Extended.Svg.SKSvg CreateSKSvg()
{
var stream = GetEmbeddedResourceStream();
var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(stream);
return svg;
}
在專案中建立一個 Images 目錄,加入一個 SVG 檔案,並且將這個檔案設定為內嵌資源,修改 MainPage.xaml,利用新增的 EmbeddedResource 屬性傳入 svg 檔案的路徑。若要啟用操作的功能,請設定 EnableTouchEvents Property 為 true,並且委派函式給 Touch event。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SvgControlSample01"
x:Class="SvgControlSample01.MainPage"
BackgroundColor="Black">
<Grid>
<Grid.Resources >
<ResourceDictionary >
<Style TargetType="local:SvgControl">
<Setter Property="Margin" Value="12"/>
<Setter Property="BackgroundColor" Value="Green"/>
<Setter Property="VerticalOptions" Value="Start"/>
<Setter Property="HorizontalOptions" Value="Start"/>
<Setter Property="EmbeddedResource" Value="SvgControlSample01.Images.pig.svg"/>
<Setter Property="EnableTouchEvents" Value="true"/>
</Style>
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions >
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:SvgControl Grid.Row="0" WidthRequest="32" HeightRequest="32" Touch="SvgControl_Touch" />
<local:SvgControl Grid.Row="1" WidthRequest="64" HeightRequest="64" />
<local:SvgControl Grid.Row="2" WidthRequest="128" HeightRequest="128" />
</Grid>
</ContentPage>
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
async private void SvgControl_Touch(object sender, SkiaSharp.Views.Forms.SKTouchEventArgs e)
{
await DisplayAlert("Test", "Click from svg control", "OK");
}
}
最後的成果如圖