Puntos y guiones en SkiaSharp

Descargar ejemploDescargar el ejemplo

Dominar las complejidades del dibujo de líneas punteadas y discontinuas en SkiaSharp

SkiaSharp permite dibujar líneas que no son sólidas, sino que se componen de puntos y guiones:

Línea de puntos

Esto se hace con un efecto de ruta de acceso, que es una instancia de la SKPathEffect clase que establece en la PathEffect propiedad de .SKPaint Puede crear un efecto de ruta de acceso (o combinar efectos de ruta de acceso) mediante uno de los métodos de creación estáticos definidos por SKPathEffect. (SKPathEffect es uno de los seis efectos admitidos por SkiaSharp; los demás se describen en la sección Efecto SkiaSharp).

Para dibujar líneas punteadas o discontinuas, use el SKPathEffect.CreateDash método estático. Hay dos argumentos: este primero es una matriz de float valores que indican las longitudes de los puntos y guiones y la longitud de los espacios entre ellos. Esta matriz debe tener un número par de elementos y debe haber al menos dos elementos. (Puede haber cero elementos en la matriz, pero eso da como resultado una línea sólida). Si hay dos elementos, el primero es la longitud de un punto o guión, y el segundo es la longitud de la brecha antes del siguiente punto o guión. Si hay más de dos elementos, se encuentran en este orden: longitud del guión, longitud del intervalo, longitud del guión, longitud del guión, longitud de la brecha, etc.

Por lo general, querrá hacer que el guión y las longitudes de espacio sean un múltiplo del ancho del trazo. Si el ancho del trazo es de 10 píxeles, por ejemplo, la matriz { 10, 10 } dibujará una línea de puntos donde los puntos y los huecos tienen la misma longitud que el grosor del trazo.

Sin embargo, la StrokeCap configuración del SKPaint objeto también afecta a estos puntos y guiones. Como verá en breve, esto tiene un impacto en los elementos de esta matriz.

Las líneas punteadas y discontinuas se muestran en la página Puntos y guiones . El archivo DotsAndDashesPage.xaml crea una instancia de dos Picker vistas, una para permitirle seleccionar un límite de trazos y el segundo para seleccionar una matriz de guiones:

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

Los tres primeros elementos del dashArrayPicker presuponen que el ancho del trazo es de 10 píxeles. La matriz { 10, 10 } es para una línea de puntos, { 30, 10 } es para una línea discontinua y { 10, 10, 30, 10 } es para una línea de puntos y guiones. (Los otros tres se discutirán en breve).

El DotsAndDashesPage archivo de código subyacente contiene el PaintSurface controlador de eventos y un par de rutinas auxiliares para acceder a las Picker vistas:

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

En las capturas de pantalla siguientes, la pantalla de iOS en el extremo izquierdo muestra una línea de puntos:

Triple captura de pantalla de la página Puntos y guiones

Sin embargo, la pantalla de Android también se supone que muestra una línea de puntos mediante la matriz { 10, 10 } pero en su lugar la línea es sólida. ¿Qué ha ocurrido? El problema es que la pantalla de Android también tiene una configuración de límites de trazos de Square. Esto extiende todos los guiones a la mitad del ancho del trazo, lo que hace que rellenen los huecos.

Para solucionar este problema cuando se usa un límite de trazo de Square o Round, debe reducir las longitudes de guion de la matriz por la longitud del trazo (a veces resultando en una longitud de guión de 0) y aumentar las longitudes de espacio por la longitud del trazo. Así se calculan las tres matrices de guiones finales del Picker archivo XAML:

  • { 10, 10 } se convierte en { 0, 20 } para una línea de puntos
  • { 30, 10 } se convierte en { 20, 20 } para una línea discontinua
  • { 10, 10, 30, 10 } se convierte en { 0, 20, 20, 20} para una línea punteada y discontinua

En la pantalla de UWP se muestra que la línea punteada y discontinua para un límite de trazo de Round. La Round tapa del trazo suele dar la mejor apariencia de puntos y guiones en líneas gruesas.

Hasta ahora no se ha hecho ninguna mención del segundo parámetro al SKPathEffect.CreateDash método . Este parámetro se denomina phase y hace referencia a un desplazamiento dentro del patrón de puntos y guiones para el principio de la línea. Por ejemplo, si la matriz de guiones es { 10, 10 } y es phase 10, la línea comienza con un intervalo en lugar de un punto.

Una aplicación interesante del phase parámetro está en una animación. La página Espiral animada es similar a la página Archimedean Spiral , salvo que la AnimatedSpiralPage clase anima el phase parámetro mediante el Xamarin.FormsDevice.Timer método :

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

Por supuesto, tendrás que ejecutar realmente el programa para ver la animación:

animada Triple de la página Espiral animada