Condividi tramite


Punti e trattini in SkiaSharp

Padroneggia le complessità del disegno di linee tratteggiate e tratteggiate in SkiaSharp

SkiaSharp consente di disegnare linee non solide, ma composte da punti e trattini:

Linea tratteggiata

Questa operazione viene eseguita con un effetto di percorso, ovvero un'istanza della SKPathEffect classe impostata sulla PathEffect proprietà di SKPaint. È possibile creare un effetto di percorso (o combinare gli effetti del percorso) usando uno dei metodi di creazione statici definiti da SKPathEffect. (SKPathEffect è uno dei sei effetti supportati da SkiaSharp; gli altri sono descritti nella sezione Effetto SkiaSharp.

Per disegnare linee tratteggiate o tratteggiate, utilizzare il SKPathEffect.CreateDash metodo statico. Esistono due argomenti: questo primo è una matrice di float valori che indicano le lunghezze dei punti e dei trattini e la lunghezza degli spazi tra di essi. Questa matrice deve avere un numero pari di elementi e deve essere presente almeno due elementi. Nella matrice possono essere presenti zero elementi, ma ciò comporta una linea continua. Se sono presenti due elementi, il primo è la lunghezza di un punto o un trattino e il secondo è la lunghezza del gap prima del punto o trattino successivo. Se sono presenti più di due elementi, sono in questo ordine: lunghezza trattino, lunghezza distanza, lunghezza trattino, lunghezza del trattino, lunghezza distanza e così via.

In genere, è consigliabile fare in modo che il trattino e la lunghezza del gap sia un multiplo della larghezza del tratto. Se la larghezza del tratto è 10 pixel, ad esempio, la matrice { 10, 10 } disegnerà una linea punteggiata in cui i punti e gli spazi vuoti sono la stessa lunghezza dello spessore del tratto.

Tuttavia, l'impostazione StrokeCap dell'oggetto SKPaint influisce anche su questi punti e trattini. Come si vedrà a breve, questo ha un impatto sugli elementi di questa matrice.

Le linee tratteggiate e tratteggiate vengono illustrate nella pagina Punti e trattini . Il file DotsAndDashesPage.xaml crea un'istanza di due Picker visualizzazioni, una per consentire di selezionare un limite di tratto e la seconda per selezionare una matrice trattino:

<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>

I primi tre elementi nell'oggetto dashArrayPicker presuppongono che la larghezza del tratto sia di 10 pixel. La matrice { 10, 10 } è per una linea punteggiata, { 30, 10 } è per una linea tratteggiata e { 10, 10, 30, 10 } è per una linea punto e trattino. Gli altri tre verranno discussi a breve.

Il DotsAndDashesPage file code-behind contiene il PaintSurface gestore eventi e un paio di routine helper per l'accesso alle Picker visualizzazioni:

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;
}

Negli screenshot seguenti la schermata iOS a sinistra mostra una linea tratteggiata:

Screenshot triplo della pagina Punti e trattini

Tuttavia, la schermata Android dovrebbe anche mostrare una linea punteggiata usando la matrice { 10, 10 } ma invece la linea è continua. Che cosa è successo? Il problema è che la schermata Android ha anche un'impostazione di limite di tratto di Square. Questo estende tutti i trattini per metà della larghezza del tratto, causando il riempimento delle lacune.

Per aggirare questo problema quando si usa un limite di tratto di Square o Round, è necessario ridurre le lunghezze del trattino nella matrice in base alla lunghezza del tratto (a volte determinando una lunghezza del trattino pari a 0) e aumentare la lunghezza del gap in base alla lunghezza del tratto. Ecco come sono state calcolate le tre matrici tratteggiate finali nel Picker file XAML:

  • { 10, 10 } diventa { 0, 20 } per una linea tratteggiata
  • { 30, 10 } diventa { 20, 20 } per una linea tratteggiata
  • { 10, 10, 30, 10 } diventa { 0, 20, 20, 20} per una linea tratteggiata e tratteggiata

La schermata UWP mostra che la linea tratteggiata e tratteggiata per un limite di tratto di Round. L'estremità Round del tratto spesso dà l'aspetto migliore di punti e trattini in linee spesse.

Finora non è stata fatta alcuna menzione del secondo parametro per il SKPathEffect.CreateDash metodo . Questo parametro è denominato phase e fa riferimento a un offset all'interno del motivo punto e trattino per l'inizio della linea. Ad esempio, se la matrice di trattini è { 10, 10 } e phase è 10, la riga inizia con una distanza anziché un punto.

Un'applicazione interessante del phase parametro è in un'animazione. La pagina Spirale animata è simile alla pagina Spiralean Archimedean, ad eccezione del fatto che la AnimatedSpiralPage classe anima il phase parametro usando il Xamarin.FormsDevice.Timer metodo :

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;
        });
    }
    ···  
}

Naturalmente, dovrai eseguire effettivamente il programma per visualizzare l'animazione:

Screenshot triplo della pagina Spirale animata