根據微軟官方文件 SkiaSharp 簡介 上的說明,SkiaSharp 提供豐富且功能強大 2D 圖形 API,可用來呈現到 2D 的緩衝區。 您可以使用這些來實作自訂使用者介面項目和可整合到您的應用程式的 2D 圖形。 SkiaSharp 是.NET 繫結至Skia程式庫會繼承此文件庫的強大的功能。這也就是說,您可以建立一個 SKCanvasView 控制項,我們便擁有了一個畫布,使用 SkiaSharp 所提供的相關 API,即可以開發出繪製出各種效果的圖片。
在這篇文章中,我們將會 使用 MVVM 設計方式,在 ViewModel 內來呼叫 SkiaSharp API,而在前一篇文章中,我們都是在頁面的 Code Behind 內撰寫呼叫 SkiaSharp 程式碼。
在這個練習專案頁面中,我們設計了兩個 200x200 的 SKCanvasView 控制項,並且使用了 Prism 所提供的 行為 Behavior 功能中的 EventToCommandBehavior,讓我們指定了這些 SKCanvasView 控制項內的指定事件 PaintSurface 若被觸發的話,將會自動去執行 ViewModel 內的 PaintSurfaceCommand 命令。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:behavior="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
xmlns:local="clr-namespace:XFSkiaMVVM"
x:Class="XFSkiaMVVM.Views.MainPage"
Title="{Binding Title}">
<ContentPage.Resources>
<ResourceDictionary>
<local:SKPaintSurfaceEventArgsConverter x:Key="SKPaintSurfaceEventArgsConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView
>
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<BoxView
Color="Black"
WidthRequest="200" HeightRequest="200"/>
<skia:SKCanvasView
BackgroundColor="Black"
IgnorePixelScaling="True"
WidthRequest="200" HeightRequest="200"
>
<skia:SKCanvasView.Behaviors>
<behavior:EventToCommandBehavior
EventName="PaintSurface"
Command="{Binding PaintSurfaceCommand}"
EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}"
/>
</skia:SKCanvasView.Behaviors>
</skia:SKCanvasView>
<skia:SKCanvasView
BackgroundColor="Black"
IgnorePixelScaling="False"
WidthRequest="200" HeightRequest="200"
>
<skia:SKCanvasView.Behaviors>
<behavior:EventToCommandBehavior
EventName="PaintSurface"
Command="{Binding PaintSurfaceSelfScaleCommand}"
EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}"
/>
</skia:SKCanvasView.Behaviors>
</skia:SKCanvasView>
</StackLayout>
</ScrollView>
</ContentPage>
不過,在這裡,您也看到 EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}" 這個 XAML 敘述,底下將會是這個 數值轉換器 Value Converter 之 SKPaintSurfaceEventArgsConverter 定義程式碼。由於我們需要在 ViewModel 內取得該事件傳入進來的參數,因此,我們建立一個數值轉換器,並且使用 EventArgsConverter 來指定要將事件傳入進來的參數,可以我們在 ViewModel 內來使用。我們可以看到,我們會透過數值轉換器傳入進來的 value 參數值,轉型成為該事件的參數型別,也就是,SKPaintSurfaceEventArgs,並且將這個值回傳回去。
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Xamarin.Forms;
namespace XFSkiaMVVM
{
class SKPaintSurfaceEventArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var skPaintSurfaceEventArgs = value as SKPaintSurfaceEventArgs;
if (skPaintSurfaceEventArgs == null)
{
throw new ArgumentException("Expected value to be of type SKPaintSurfaceEventArgs", nameof(value));
}
return skPaintSurfaceEventArgs;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
那麼,究竟要如何在 ViewModel 內讀取該事件傳入進來的參數值呢?在這裡,當我們要宣告事件2命令的命令時候,要使用泛型的方式來宣告其型別,也就是類似這樣
public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceCommand { get; set; }
在這裡,我們就看到了剛剛的數值轉換器的轉型型別,也就是我們命令中的要指定的泛型型別。接下來,在我們建立這個命令的物件時候,就可以使用 PaintSurfaceCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args => { ... }
這樣敘述來建立,我們使用了 Lambda 匿名函式傳入到這個命令建構式內,而我們在這個匿名函式內,就可以透過 args 這個參數變數,取得原先在 SKCanvasView 控制項 PaintSurface 事件內的事件參數物件。有了 SKPaintSurfaceEventArgs
這個參數物件,我們便可以開始進行針對 SKCanvasView 畫布,呼叫各種 SkiaSharp API,進行繪圖需求工作了。
若您仔細觀察執行結果,您將會發現到第一個 SKCanvasView 控制項所繪製出來的圓形與線條會有鋸齒狀,而且,這裡所指定畫線與畫圓的 API 使用到的尺寸都是 與裝置無關的畫素,也就是沒有根據當時螢幕的縮放比來自行計算出真實畫素實際值,這是因為,我們在 SKCanvasView 控制項內,設定了
IgnorePixelScaling="True"
屬性值,當 IgnorePixelScaling 屬性值為真,我們在 SKCanvasView 控制項內的所指定的尺寸,就可以使用 XAML 中設定的尺寸大小, SkiaSharp 會自動幫我們進行縮放,可是,您看到這樣的效果並不是很好,因為,會有鋸齒狀出現。
所以,我們還是建議類似 PaintSurfaceSelfScaleCommand 命令中的做法,使用
float fooScale = info.Width / 200.0f; canvas.Scale(fooScale);
敘述,呼叫 Scale 方法,指定縮放比率值,這樣就不會有鋸齒狀出現了。using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace XFSkiaMVVM.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceCommand { get; set; }
public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceSelfScaleCommand { get; set; }
private readonly INavigationService _navigationService;
public MainPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
PaintSurfaceCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args =>
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint fooCirclePaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.DeepSkyBlue,
StrokeWidth=1,
};
SKPaint fooLinePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 1,
};
canvas.DrawCircle(100, 100, 100, fooCirclePaint);
canvas.DrawLine(0, 0, 200, 140, fooLinePaint);
});
PaintSurfaceSelfScaleCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args =>
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float fooScale = info.Width / 200.0f;
SKPaint fooCirclePaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.DeepSkyBlue,
StrokeWidth = 1,
};
SKPaint fooLinePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 1,
};
canvas.Scale(fooScale);
canvas.DrawCircle(100, 100, 100, fooCirclePaint);
canvas.DrawLine(0, 0, 200, 140, fooLinePaint);
});
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
}
- Android 平台執行結果
- UWP 平台執行結果
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程