Partilhar via


Unidades independentes de dispositivo e pixels

Explore as diferenças entre coordenadas e Xamarin.Forms coordenadas do SkiaSharp

Este artigo explora as diferenças no sistema de coordenadas usado no SkiaSharp e Xamarin.Formsno . Você pode obter informações para converter entre os dois sistemas de coordenadas e também desenhar gráficos que preencham uma área específica:

Um oval que preenche a tela

Se você já está programando há Xamarin.Forms algum tempo, pode ter uma ideia de Xamarin.Forms coordenadas e tamanhos. Os círculos desenhados nos dois artigos anteriores podem parecer um pouco pequenos para você.

Esses círculos são pequenos em comparação com Xamarin.Forms os tamanhos. Por padrão, o SkiaSharp desenha em unidades de pixels enquanto Xamarin.Forms baseia coordenadas e tamanhos em uma unidade independente do dispositivo estabelecida pela plataforma subjacente. (Mais informações sobre o sistema de Xamarin.Forms coordenadas podem ser encontradas no Capítulo 5. Lidando com tamanhos do livro Criando aplicativos móveis com Xamarin.Forms.)

A página no programa de amostra intitulado Tamanho da superfície usa a saída de texto do SkiaSharp para mostrar o tamanho da superfície de exibição de três fontes diferentes:

  • A normal Xamarin.FormsWidth e Height as propriedades do SKCanvasView objeto.
  • A propriedade CanvasSize do objeto SKCanvasView.
  • A Size propriedade do SKImageInfo valor, que é consistente com as Width propriedades e Height usadas nas duas páginas anteriores.

A SurfaceSizePage classe mostra como exibir esses valores. O construtor salva o SKCanvasView objeto como um campo, para que ele possa ser acessado no manipulador de PaintSurface eventos:

SKCanvasView canvasView;

public SurfaceSizePage()
{
    Title = "Surface Size";

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

SKCanvas Inclui seis métodos diferentes DrawText , mas este DrawText método é o mais simples:

public void DrawText (String text, Single x, Single y, SKPaint paint)

Você especifica a cadeia de caracteres de texto, as coordenadas X e Y onde o texto deve começar e um SKPaint objeto. A coordenada X especifica onde o lado esquerdo do texto está posicionado, mas cuidado: A coordenada Y especifica a posição da linha de base do texto. Se você já escreveu à mão em papel pautado, a linha de base é a linha na qual os caracteres ficam e abaixo da qual os descendentes (como os das letras g, p, q e y) descem.

O SKPaint objeto permite especificar a cor do texto, a família de fontes e o tamanho do texto. Por padrão, a propriedade tem um valor de 12, o TextSize que resulta em texto minúsculo em dispositivos de alta resolução, como telefones. Em qualquer coisa, exceto nos aplicativos mais simples, você também precisará de algumas informações sobre o tamanho do texto que está exibindo. A SKPaint classe define uma FontMetrics propriedade e vários MeasureText métodos, mas para necessidades menos sofisticadas, a FontSpacing propriedade fornece um valor recomendado para espaçar linhas sucessivas de texto.

O manipulador a seguir PaintSurface cria um SKPaint objeto para um TextSize de 40 pixels, que é a altura vertical desejada do texto da parte superior dos ascendentes até a parte inferior dos descendentes. O FontSpacing valor que o objeto retorna é um pouco maior que isso SKPaint , cerca de 47 pixels.

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

    canvas.Clear();

    SKPaint paint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 40
    };

    float fontSpacing = paint.FontSpacing;
    float x = 20;               // left margin
    float y = fontSpacing;      // first baseline
    float indent = 100;

    canvas.DrawText("SKCanvasView Height and Width:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(String.Format("{0:F2} x {1:F2}",
                                  canvasView.Width, canvasView.Height),
                    x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKCanvasView CanvasSize:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(canvasView.CanvasSize.ToString(), x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKImageInfo Size:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(info.Size.ToString(), x + indent, y, paint);
}

O método começa a primeira linha de texto com uma coordenada X de 20 (para uma pequena margem à esquerda) e uma coordenada Y de , que é um pouco mais do fontSpacingque o necessário para exibir a altura total da primeira linha de texto na parte superior da superfície de exibição. Após cada chamada para DrawText, a coordenada Y é aumentada em um ou dois incrementos de fontSpacing.

Este é o programa em execução:

As capturas de tela mostram o aplicativo Tamanho do Surface em execução em dois dispositivos móveis.

Como você pode ver, a CanvasSize propriedade do e a SKCanvasView Size propriedade do valor são consistentes no relatório das dimensões em SKImageInfo pixels. As Height propriedades e Width das SKCanvasView propriedades are Xamarin.Forms e relatam o tamanho da exibição nas unidades independentes de dispositivo definidas pela plataforma.

O simulador iOS sete à esquerda tem dois pixels por unidade independente de dispositivo e o Android Nexus 5 no centro tem três pixels por unidade. É por isso que o círculo simples mostrado anteriormente tem tamanhos diferentes em plataformas diferentes.

Se preferir trabalhar inteiramente em unidades independentes de dispositivo, você pode fazer isso definindo a IgnorePixelScaling propriedade do SKCanvasView .true No entanto, você pode não gostar dos resultados. O SkiaSharp renderiza os gráficos em uma superfície de dispositivo menor, com um tamanho de pixel igual ao tamanho da exibição em unidades independentes do dispositivo. (Por exemplo, o SkiaSharp usaria uma superfície de exibição de 360 x 512 pixels no Nexus 5.) Em seguida, ele aumenta o tamanho da imagem, resultando em imperfeições de bitmap perceptíveis.

Para manter a mesma resolução de imagem, uma solução melhor é escrever suas próprias funções simples para converter entre os dois sistemas de coordenadas.

Além do DrawCircle método, SKCanvas também define dois DrawOval métodos que desenham uma elipse. Uma elipse é definida por dois raios em vez de um único raio. Estes são conhecidos como raio maior e raio menor. O DrawOval método desenha uma elipse com os dois raios paralelos aos eixos X e Y. (Se você precisar desenhar uma elipse com eixos que não são paralelos aos eixos X e Y, poderá usar uma transformação de rotação conforme discutido no artigo A Transformação de rotação ou um caminho gráfico, conforme discutido no artigo Três maneiras de desenhar um arco). Essa sobrecarga do método nomeia os dois parâmetros rx de DrawOval raios e ry indica que eles são paralelos aos eixos X e Y:

public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)

É possível desenhar uma elipse que preencha a superfície da tela? A página Preenchimento de elipse demonstra como. O PaintSurface manipulador de eventos na classe EllipseFillPage.xaml.cs subtrai metade da largura do traço dos xRadius valores e yRadius para ajustar toda a elipse e seu contorno dentro da superfície de exibição:

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

    canvas.Clear();

    float strokeWidth = 50;
    float xRadius = (info.Width - strokeWidth) / 2;
    float yRadius = (info.Height - strokeWidth) / 2;

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = strokeWidth
    };
    canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
}

Aqui está rodando:

As capturas de tela mostram o aplicativo Ellipse Fill em execução em dois dispositivos móveis.

O outro DrawOval método tem um SKRect argumento, que é um retângulo definido em termos das coordenadas X e Y de seu canto superior esquerdo e canto inferior direito. O oval preenche esse retângulo, o que sugere que pode ser possível usá-lo na página Preenchimento de elipse assim:

SKRect rect = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawOval(rect, paint);

No entanto, isso trunca todas as bordas do contorno da elipse nos quatro lados. Você precisa ajustar todos os argumentos do SKRect construtor com base no strokeWidth para que isso funcione corretamente:

SKRect rect = new SKRect(strokeWidth / 2,
                         strokeWidth / 2,
                         info.Width - strokeWidth / 2,
                         info.Height - strokeWidth / 2);
canvas.DrawOval(rect, paint);