SkiaSharp の点線と破線

Download Sampleサンプルのダウンロード

SkiaSharp で点線と破線を描画する複雑さをマスターする

SkiaSharp を使用すると、以下のように実線ではなくドットとダッシュで構成される線を描画できます。

Dotted line

これを行うには、"パス効果"を使用します。パス効果は、SKPaintPathEffect プロパティに設定された SKPathEffect クラスのインスタンスです。 SKPathEffect で定義されている静的作成メソッドのいずれかを使用して、パス効果を作成するか、複数のパス効果を組み合わせることができます (SKPathEffect は、SkiaSharp でサポートされている 6 つの効果のうちの 1 つです。その他の効果については、「SkiaSharp 効果」セクションで説明します)。

点線または破線を描画するには、SKPathEffect.CreateDash 静的メソッドを使用します。 このメソッドには、2 つの引数があります。1 つ目は、ドットおよびダッシュの長さと、それらの間の空白部分の長さを示す float 値の配列です。 この配列には、偶数かつ 2 つ以上の要素が存在する必要があります (配列の要素を 0 個にすることもできますが、この場合は実線になります)。要素が 2 つある場合、1 つ目はドットまたはダッシュの長さ、2 つ目は次のドットまたはダッシュまでの空白の長さを示します。 要素が 3 つ以上ある場合は、ダッシュの長さ、空白の長さ、ダッシュの長さ、空白の長さという順序になります。

一般的に、ダッシュと空白の長さは線幅の倍数にする必要があります。 たとえば線幅が 10 ピクセルの場合、配列を { 10, 10 } に指定すると、ドットと空白が線の太さと同じ長さになる点線を描画します。

ただし、SKPaint オブジェクトの StrokeCap 設定も、これらのドットとダッシュに影響を与えます。 すぐにわかりますが、この設定がこの配列の要素に影響を与えます。

点線と破線は、[ドットとダッシュ] ページに表示されます。 DotsAndDashesPage.xaml ファイルでは、以下のように 2 つの Picker ビューをインスタンス化します。1 つのビューで線端を選択し、もう一つのビューでダッシュ配列を選択します。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
             xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Paths.DotsAndDashesPage"
             Title="Dots and Dashes">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Picker x:Name="strokeCapPicker"
                Title="Stroke Cap"
                Grid.Row="0"
                Grid.Column="0"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKStrokeCap}">
                    <x:Static Member="skia:SKStrokeCap.Butt" />
                    <x:Static Member="skia:SKStrokeCap.Round" />
                    <x:Static Member="skia:SKStrokeCap.Square" />
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Picker x:Name="dashArrayPicker"
                Title="Dash Array"
                Grid.Row="0"
                Grid.Column="1"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>10, 10</x:String>
                    <x:String>30, 10</x:String>
                    <x:String>10, 10, 30, 10</x:String>
                    <x:String>0, 20</x:String>
                    <x:String>20, 20</x:String>
                    <x:String>0, 20, 20, 20</x:String>
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <skiaforms:SKCanvasView x:Name="canvasView"
                                PaintSurface="OnCanvasViewPaintSurface"
                                Grid.Row="1"
                                Grid.Column="0"
                                Grid.ColumnSpan="2" />
    </Grid>
</ContentPage>

dashArrayPicker の最初の 3 つの項目は、 線幅が 10 ピクセルであると想定しています。 { 10, 10 } 配列は点線、{ 30, 10 } は破線、{ 10, 10, 30, 10 } は点線と破線の組み合わせです (他の 3 つの項目についても、追って説明します)。

DotsAndDashesPage 分離コード ファイルには、PaintSurface イベント ハンドラーと、Picker ビューにアクセスするためのいくつかのヘルパー ルーチンが含まれています。

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = 10,
        StrokeCap = (SKStrokeCap)strokeCapPicker.SelectedItem,
        PathEffect = SKPathEffect.CreateDash(GetPickerArray(dashArrayPicker), 20)
    };

    SKPath path = new SKPath();
    path.MoveTo(0.2f * info.Width, 0.2f * info.Height);
    path.LineTo(0.8f * info.Width, 0.8f * info.Height);
    path.LineTo(0.2f * info.Width, 0.8f * info.Height);
    path.LineTo(0.8f * info.Width, 0.2f * info.Height);

    canvas.DrawPath(path, paint);
}

float[] GetPickerArray(Picker picker)
{
    if (picker.SelectedIndex == -1)
    {
        return new float[0];
    }

    string str = (string)picker.SelectedItem;
    string[] strs = str.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
    float[] array = new float[strs.Length];

    for (int i = 0; i < strs.Length; i++)
    {
        array[i] = Convert.ToSingle(strs[i]);
    }
    return array;
}

次のスクリーンショットでは、左の iOS 画面に点線が表示されています。

Triple screenshot of the Dots and Dashes page

Android 画面でも配列 { 10, 10 } を使用すれば点線が表示されることになっていますが、実線になっています。 何が起きたのか? この問題は、Android の画面に Square という線端の設定が含まれていることにあります。 この設定によりすべてのダッシュが線幅の半分の長さ分だけ伸び、これにより空白部分が埋まったのです。

線端に Square または Round を使用する場合にこの問題を回避するには、配列でダッシュの長さを線の長さ分短くし (これによりダッシュの長さが 0 になる場合があります)、空白部分の長さを線の長さ分長くする必要があります。 次に、XAML ファイル内の Picker の残りの 3 つのダッシュの配列方法を計算しました。

  • 点線にするには、{ 10, 10 } を { 0, 20 } にします
  • 破線にするには、{ 30, 10 } を { 20, 20 } にします
  • 点線と破線の組み合わせにするには、{ 10, 10, 30, 10 } を { 0, 20, 20, 20} にします

UWP 画面には、Round の線端に対応する点線と破線が表示されます。 多くの場合、Round 線端を使用すると、ドットとダッシュが太線で最もきれいに表示されます。

SKPathEffect.CreateDash メソッドに対する 2 つ目のパラメーターについて、まだ説明していませんでした。 このパラメーターは phase という名前で、線の先頭のドットとダッシュのパターン内でオフセットを参照します。 たとえば、ダッシュ配列が { 10, 10 } で phase が 10 の場合、線はドットではなく空白で始まります。

phase パラメーターの中でも興味深いアプリケーションの 1 つが、アニメーションです。 [アニメーション スパイラル] ページは[螺線] ページに似ていますが、AnimatedSpiralPage クラスで Xamarin.FormsDevice.Timer メソッドを使用して phase パラメーターをアニメーション化している点が異なります。

public class AnimatedSpiralPage : ContentPage
{
    const double cycleTime = 250;       // in milliseconds

    SKCanvasView canvasView;
    Stopwatch stopwatch = new Stopwatch();
    bool pageIsActive;
    float dashPhase;

    public AnimatedSpiralPage()
    {
        Title = "Animated Spiral";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        pageIsActive = true;
        stopwatch.Start();

        Device.StartTimer(TimeSpan.FromMilliseconds(33), () =>
        {
            double t = stopwatch.Elapsed.TotalMilliseconds % cycleTime / cycleTime;
            dashPhase = (float)(10 * t);
            canvasView.InvalidateSurface();

            if (!pageIsActive)
            {
                stopwatch.Stop();
            }

            return pageIsActive;
        });
    }
    ···  
}

もちろん、次のようなアニメーションを表示するには、プログラムを実際に実行する必要があります。

Triple screenshot of the Animated Spiral page