Mosaico de bitmap SkiaSharp
Como você viu nos dois artigos anteriores, a SKShader
classe pode criar gradientes lineares ou circulares. Este artigo se concentra no SKShader
objeto que usa um bitmap para lado a lado de uma área. O bitmap pode ser repetido horizontal e verticalmente, em sua orientação original ou alternadamente invertido horizontal e verticalmente. O inversão evita descontinuidades entre as telhas:
O método estático SKShader.CreateBitmap
que cria esse sombreador tem um SKBitmap
parâmetro e dois membros da SKShaderTileMode
enumeração:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
Os dois parâmetros indicam os modos usados para mosaico horizontal e mosaico vertical. Essa é a mesma SKShaderTileMode
enumeração que também é usada com os métodos de gradiente.
Uma CreateBitmap
sobrecarga inclui um SKMatrix
argumento para executar uma transformação nos bitmaps lado a lado:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
Este artigo contém vários exemplos de como usar essa transformação de matriz com bitmaps lado a lado.
Explorando os modos de bloco
O primeiro programa na seção Bitmap Tiling da página Shaders and other Effects do exemplo demonstra os efeitos dos dois SKShaderTileMode
argumentos. O arquivo XAML Bitmap Tile Flip Modes instancia um SKCanvasView
e dois Picker
modos de exibição que permitem selecionar um SKShaderTilerMode
valor para mosaico horizontal e vertical. Observe que uma matriz dos SKShaderTileMode
membros é definida na Resources
seção:
<?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;assembly=SkiaSharp"
xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Effects.BitmapTileFlipModesPage"
Title="Bitmap Tile Flip Modes">
<ContentPage.Resources>
<x:Array x:Key="tileModes"
Type="{x:Type skia:SKShaderTileMode}">
<x:Static Member="skia:SKShaderTileMode.Clamp" />
<x:Static Member="skia:SKShaderTileMode.Repeat" />
<x:Static Member="skia:SKShaderTileMode.Mirror" />
</x:Array>
</ContentPage.Resources>
<StackLayout>
<skiaforms:SKCanvasView x:Name="canvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Picker x:Name="xModePicker"
Title="Tile X Mode"
Margin="10, 0"
ItemsSource="{StaticResource tileModes}"
SelectedIndex="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged" />
<Picker x:Name="yModePicker"
Title="Tile Y Mode"
Margin="10, 10"
ItemsSource="{StaticResource tileModes}"
SelectedIndex="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged" />
</StackLayout>
</ContentPage>
O construtor do arquivo code-behind é carregado no recurso de bitmap que mostra um macaco sentado. Primeiro, ele corta a imagem usando o ExtractSubset
método de SKBitmap
modo que a cabeça e os pés estejam tocando as bordas do bitmap. O construtor, em seguida, usa o Resize
método para criar outro bitmap de metade do tamanho. Essas alterações tornam o bitmap um pouco mais adequado para o lado a lado:
public partial class BitmapTileFlipModesPage : ContentPage
{
SKBitmap bitmap;
public BitmapTileFlipModesPage ()
{
InitializeComponent ();
SKBitmap origBitmap = BitmapExtensions.LoadBitmapResource(
GetType(), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
// Define cropping rect
SKRectI cropRect = new SKRectI(5, 27, 296, 260);
// Get the cropped bitmap
SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height);
origBitmap.ExtractSubset(croppedBitmap, cropRect);
// Resize to half the width and height
SKImageInfo info = new SKImageInfo(cropRect.Width / 2, cropRect.Height / 2);
bitmap = croppedBitmap.Resize(info, SKBitmapResizeMethod.Box);
}
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
canvasView.InvalidateSurface();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Get tile modes from Pickers
SKShaderTileMode xTileMode =
(SKShaderTileMode)(xModePicker.SelectedIndex == -1 ?
0 : xModePicker.SelectedItem);
SKShaderTileMode yTileMode =
(SKShaderTileMode)(yModePicker.SelectedIndex == -1 ?
0 : yModePicker.SelectedItem);
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(bitmap, xTileMode, yTileMode);
canvas.DrawRect(info.Rect, paint);
}
}
}
O PaintSurface
manipulador obtém as SKShaderTileMode
configurações dos dois Picker
modos de exibição e cria um SKShader
objeto com base no bitmap e nesses dois valores. Este sombreador é usado para preencher a tela:
A tela do iOS à esquerda mostra o efeito dos valores padrão de SKShaderTileMode.Clamp
. O bitmap fica no canto superior esquerdo. Abaixo do bitmap, a linha inferior de pixels é repetida até o fim. À direita do bitmap, a coluna mais à direita de pixels é repetida por todo o caminho. O restante da tela é colorido pelo pixel marrom escuro no canto inferior direito do bitmap. Deve ser óbvio que a Clamp
opção quase nunca é usada com bitmap tiling!
A tela do Android no centro mostra o resultado de SKShaderTileMode.Repeat
para ambos os argumentos. A telha é repetida horizontal e verticalmente. A tela da Plataforma Universal do Windows mostra SKShaderTileMode.Mirror
. Os blocos são repetidos, mas alternadamente invertidos horizontal e verticalmente. A vantagem dessa opção é que não há descontinuidades entre as telhas.
Tenha em mente que você pode usar diferentes opções para a repetição horizontal e vertical. Você pode especificar SKShaderTileMode.Mirror
como o segundo argumento para CreateBitmap
, mas SKShaderTileMode.Repeat
como o terceiro argumento. Em cada fila, os macacos ainda alternam entre a imagem normal e a imagem espelhada, mas nenhum dos macacos está de cabeça para baixo.
Planos de fundo padronizados
O mosaico de bitmap é comumente usado para criar um plano de fundo padronizado a partir de um bitmap relativamente pequeno. O exemplo clássico é uma parede de tijolos.
A página Algorithmic Brick Wall cria um pequeno bitmap que se assemelha a um tijolo inteiro e duas metades de um tijolo separadas por argamassa. Como esse tijolo também é usado no próximo exemplo, ele é criado por um construtor estático e tornado público com uma propriedade estática:
public class AlgorithmicBrickWallPage : ContentPage
{
static AlgorithmicBrickWallPage()
{
const int brickWidth = 64;
const int brickHeight = 24;
const int morterThickness = 6;
const int bitmapWidth = brickWidth + morterThickness;
const int bitmapHeight = 2 * (brickHeight + morterThickness);
SKBitmap bitmap = new SKBitmap(bitmapWidth, bitmapHeight);
using (SKCanvas canvas = new SKCanvas(bitmap))
using (SKPaint brickPaint = new SKPaint())
{
brickPaint.Color = new SKColor(0xB2, 0x22, 0x22);
canvas.Clear(new SKColor(0xF0, 0xEA, 0xD6));
canvas.DrawRect(new SKRect(morterThickness / 2,
morterThickness / 2,
morterThickness / 2 + brickWidth,
morterThickness / 2 + brickHeight),
brickPaint);
int ySecondBrick = 3 * morterThickness / 2 + brickHeight;
canvas.DrawRect(new SKRect(0,
ySecondBrick,
bitmapWidth / 2 - morterThickness / 2,
ySecondBrick + brickHeight),
brickPaint);
canvas.DrawRect(new SKRect(bitmapWidth / 2 + morterThickness / 2,
ySecondBrick,
bitmapWidth,
ySecondBrick + brickHeight),
brickPaint);
}
// Save as public property for other programs
BrickWallTile = bitmap;
}
public static SKBitmap BrickWallTile { private set; get; }
···
}
O bitmap resultante tem 70 pixels de largura e 60 pixels de altura:
O restante da página Algorithmic Brick Wall cria um SKShader
objeto que repete essa imagem horizontal e verticalmente:
public class AlgorithmicBrickWallPage : ContentPage
{
···
public AlgorithmicBrickWallPage ()
{
Title = "Algorithmic Brick Wall";
// Create SKCanvasView
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(BrickWallTile,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Eis o resultado:
Você pode preferir algo um pouco mais realista. Nesse caso, você pode tirar uma fotografia de uma parede de tijolos real e, em seguida, cortá-la. Este bitmap tem 300 pixels de largura e 150 pixels de altura:
Este bitmap é usado na página Parede de tijolos fotográficos:
public class PhotographicBrickWallPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(PhotographicBrickWallPage),
"SkiaSharpFormsDemos.Media.BrickWallTile.jpg");
public PhotographicBrickWallPage()
{
Title = "Photographic Brick Wall";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Observe que os SKShaderTileMode
argumentos para CreateBitmap
são ambos Mirror
. Essa opção geralmente é necessária quando você usa blocos criados a partir de imagens do mundo real. O espelhamento dos blocos evita descontinuidades:
Algum trabalho é necessário para obter um bitmap adequado para o bloco. Este não funciona muito bem porque o tijolo mais escuro se destaca demais. Ele aparece regularmente nas imagens repetidas, revelando o fato de que esta parede de tijolos foi construída a partir de um bitmap menor.
A pasta Mídia do exemplo também inclui esta imagem de uma parede de pedra:
No entanto, o bitmap original é um pouco grande demais para um bloco. Ele pode ser redimensionado, mas o SKShader.CreateBitmap
método também pode redimensionar o bloco aplicando uma transformação a ele. Esta opção é demonstrada na página Parede de Pedra:
public class StoneWallPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(StoneWallPage),
"SkiaSharpFormsDemos.Media.StoneWallTile.jpg");
public StoneWallPage()
{
Title = "Stone Wall";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
// Create scale transform
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
// Create bitmap tiling
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror,
matrix);
// Draw background
canvas.DrawRect(info.Rect, paint);
}
}
}
Um SKMatrix
valor é criado para dimensionar a imagem para metade de seu tamanho original:
A transformação opera no bitmap original usado no CreateBitmap
método? Ou transforma a matriz resultante de blocos?
Uma maneira fácil de responder a essa pergunta é incluir uma rotação como parte da transformação:
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
Se a transformação for aplicada ao bloco individual, cada imagem repetida do bloco deverá ser girada e o resultado conterá muitas descontinuidades. Mas é óbvio a partir desta captura de tela que a matriz composta de blocos é transformada:
Na seção Alinhamento de blocos, você verá um exemplo de transformação de conversão aplicada ao sombreador.
O exemplo simula um plano de fundo de grão de madeira usando mosaico de bitmap com base nesse bitmap quadrado de 240 pixels:
Trata-se de uma fotografia de um piso de madeira. A SKShaderTileMode.Mirror
opção permite que ele apareça como uma área muito maior de madeira:
Alinhamento de blocos
Todos os exemplos mostrados até agora usaram o sombreador criado por SKShader.CreateBitmap
para cobrir toda a tela. Na maioria dos casos, você usará mosaico de bitmap para arquivar áreas menores ou (mais raramente) para preencher o interior de linhas grossas. Aqui está o revestimento fotográfico de parede de tijolos usado para um retângulo menor:
Isso pode parecer bom para você, ou talvez não. Talvez você esteja incomodado com o fato de o padrão de azulejos não começar com um tijolo cheio no canto superior esquerdo do retângulo. Isso porque os sombreadores estão alinhados com a tela e não com o objeto gráfico que eles adornam.
A correção é simples. Crie um SKMatrix
valor com base em uma transformação de tradução. A transformação efetivamente desloca o padrão lado a lado para o ponto em que você deseja que o canto superior esquerdo do bloco seja alinhado. Essa abordagem é demonstrada na página Alinhamento de Blocos, que criou a imagem dos blocos não alinhados mostrada acima:
public class TileAlignmentPage : ContentPage
{
bool isAligned;
public TileAlignmentPage()
{
Title = "Tile Alignment";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
// Add tap handler
TapGestureRecognizer tap = new TapGestureRecognizer();
tap.Tapped += (sender, args) =>
{
isAligned ^= true;
canvasView.InvalidateSurface();
};
canvasView.GestureRecognizers.Add(tap);
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
SKRect rect = new SKRect(info.Width / 7,
info.Height / 7,
6 * info.Width / 7,
6 * info.Height / 7);
// Get bitmap from other program
SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;
// Create bitmap tiling
if (!isAligned)
{
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat);
}
else
{
SKMatrix matrix = SKMatrix.MakeTranslation(rect.Left, rect.Top);
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
matrix);
}
// Draw rectangle
canvas.DrawRect(rect, paint);
}
}
}
A página Alinhamento de Blocos inclui um TapGestureRecognizer
arquivo . Toque ou clique na tela e o programa alterna para o SKShader.CreateBitmap
método com um SKMatrix
argumento. Essa transformação desloca o padrão para que o canto superior esquerdo contenha um bloco completo:
Você também pode usar essa técnica para garantir que o padrão de bitmap lado a lado esteja centralizado na área que ele pinta. Na página Blocos Centralizados, o PaintSurface
manipulador primeiro calcula as coordenadas como se fosse exibir o único bitmap no centro da tela. Em seguida, ele usa essas coordenadas para criar uma transformação de tradução para SKShader.CreateBitmap
. Essa transformação desloca todo o padrão para que um bloco seja centralizado:
public class CenteredTilesPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(CenteredTilesPage),
"SkiaSharpFormsDemos.Media.monkey.png");
public CenteredTilesPage ()
{
Title = "Centered Tiles";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Find coordinates to center bitmap in canvas...
float x = (info.Width - bitmap.Width) / 2f;
float y = (info.Height - bitmap.Height) / 2f;
using (SKPaint paint = new SKPaint())
{
// ... but use them to create a translate transform
SKMatrix matrix = SKMatrix.MakeTranslation(x, y);
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
matrix);
// Use that tiled bitmap pattern to fill a circle
canvas.DrawCircle(info.Rect.MidX, info.Rect.MidY,
Math.Min(info.Width, info.Height) / 2,
paint);
}
}
}
O PaintSurface
manipulador conclui desenhando um círculo no centro da tela. Com certeza, um dos azulejos está exatamente no centro do círculo, e os outros estão dispostos em um padrão simétrico:
Outra abordagem de centralização é realmente um pouco mais fácil. Em vez de construir uma transformação de tradução que coloque um bloco no centro, você pode centralizar um canto do padrão lado a lado. SKMatrix.MakeTranslation
Na chamada, use argumentos para o centro da tela:
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
O padrão ainda é centralizado e simétrico, mas nenhum bloco está no centro:
Simplificação através da rotação
Às vezes, o SKShader.CreateBitmap
uso de uma transformação de rotação no método pode simplificar o bloco de bitmap. Isso fica evidente quando se tenta definir uma telha para uma cerca de corrente. O arquivo ChainLinkTile.cs cria o bloco mostrado aqui (com um plano de fundo rosa para fins de clareza):
O bloco precisa incluir dois links, para que o código divida o bloco em quatro quadrantes. Os quadrantes superior esquerdo e inferior direito são os mesmos, mas não estão completos. Os fios têm pequenos entalhes que devem ser manuseados com algum desenho adicional nos quadrantes superior direito e inferior esquerdo. O arquivo que faz todo esse trabalho tem 174 linhas.
Acaba por ser muito mais fácil criar este bloco:
Se o sombreador de bloco de bitmap for girado 90 graus, os visuais serão quase os mesmos.
O código para criar o bloco de link de cadeia mais fácil faz parte da página Bloco de link de cadeia. O construtor determina um tamanho de bloco com base no tipo de dispositivo em que o programa está sendo executado e, em seguida, chama CreateChainLinkTile
, que se baseia no bitmap usando linhas, caminhos e sombreadores de gradiente:
public class ChainLinkFencePage : ContentPage
{
···
SKBitmap tileBitmap;
public ChainLinkFencePage ()
{
Title = "Chain-Link Fence";
// Create bitmap for chain-link tiling
int tileSize = Device.Idiom == TargetIdiom.Desktop ? 64 : 128;
tileBitmap = CreateChainLinkTile(tileSize);
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
SKBitmap CreateChainLinkTile(int tileSize)
{
tileBitmap = new SKBitmap(tileSize, tileSize);
float wireThickness = tileSize / 12f;
using (SKCanvas canvas = new SKCanvas(tileBitmap))
using (SKPaint paint = new SKPaint())
{
canvas.Clear();
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = wireThickness;
paint.IsAntialias = true;
// Draw straight wires first
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.Silver, SKColors.Black },
new float[] { 0.4f, 0.6f },
SKShaderTileMode.Clamp);
canvas.DrawLine(0, tileSize / 2,
tileSize / 2, tileSize / 2 - wireThickness / 2, paint);
canvas.DrawLine(tileSize, tileSize / 2,
tileSize / 2, tileSize / 2 + wireThickness / 2, paint);
// Draw curved wires
using (SKPath path = new SKPath())
{
path.MoveTo(tileSize / 2, 0);
path.LineTo(tileSize / 2 - wireThickness / 2, tileSize / 2);
path.ArcTo(wireThickness / 2, wireThickness / 2,
0,
SKPathArcSize.Small,
SKPathDirection.CounterClockwise,
tileSize / 2, tileSize / 2 + wireThickness / 2);
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.Silver, SKColors.Black },
null,
SKShaderTileMode.Clamp);
canvas.DrawPath(path, paint);
path.Reset();
path.MoveTo(tileSize / 2, tileSize);
path.LineTo(tileSize / 2 + wireThickness / 2, tileSize / 2);
path.ArcTo(wireThickness / 2, wireThickness / 2,
0,
SKPathArcSize.Small,
SKPathDirection.CounterClockwise,
tileSize / 2, tileSize / 2 - wireThickness / 2);
paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
new SKPoint(0, tileSize),
new SKColor[] { SKColors.White, SKColors.Silver },
null,
SKShaderTileMode.Clamp);
canvas.DrawPath(path, paint);
}
return tileBitmap;
}
}
···
}
Com exceção dos fios, a telha é transparente, o que significa que você pode exibi-la em cima de outra coisa. O programa carrega em um dos recursos de bitmap, exibe para preencher a tela e, em seguida, desenha o sombreador na parte superior:
public class ChainLinkFencePage : ContentPage
{
SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
typeof(ChainLinkFencePage), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.UniformToFill,
BitmapAlignment.Center, BitmapAlignment.Start);
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(tileBitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat,
SKMatrix.MakeRotationDegrees(45));
canvas.DrawRect(info.Rect, paint);
}
}
}
Observe que o sombreador é girado 45 graus para que ele seja orientado como uma verdadeira cerca de elo de corrente:
Animando blocos de bitmap
Você pode animar um padrão de bloco de bitmap inteiro animando a transformação de matriz. Talvez você queira que o padrão se mova horizontalmente ou verticalmente ou ambos. Você pode fazer isso criando uma transformação de tradução com base nas coordenadas variáveis.
Também é possível desenhar em um pequeno bitmap ou manipular os bits de pixel do bitmap na taxa de 60 vezes por segundo. Esse bitmap pode ser usado para mosaico, e todo o padrão lado a lado pode parecer animado.
A página Bloco de bitmap animado demonstra essa abordagem. Um bitmap é instanciado como um campo para ser quadrado de 64 pixels. O construtor chama DrawBitmap
para dar-lhe uma aparência inicial. Se o angle
campo for zero (como é quando o método é chamado pela primeira vez), o bitmap conterá duas linhas cruzadas como um X. As linhas são longas o suficiente para sempre alcançar a borda do bitmap, independentemente do angle
valor:
public class AnimatedBitmapTilePage : ContentPage
{
const int SIZE = 64;
SKCanvasView canvasView;
SKBitmap bitmap = new SKBitmap(SIZE, SIZE);
float angle;
···
public AnimatedBitmapTilePage ()
{
Title = "Animated Bitmap Tile";
// Initialize bitmap prior to animation
DrawBitmap();
// Create SKCanvasView
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
···
void DrawBitmap()
{
using (SKCanvas canvas = new SKCanvas(bitmap))
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = SIZE / 8;
canvas.Clear();
canvas.Translate(SIZE / 2, SIZE / 2);
canvas.RotateDegrees(angle);
canvas.DrawLine(-SIZE, -SIZE, SIZE, SIZE, paint);
canvas.DrawLine(-SIZE, SIZE, SIZE, -SIZE, paint);
}
}
···
}
A sobrecarga de OnAppearing
animação ocorre nas substituições e OnDisappearing
. O OnTimerTick
método anima o angle
valor de 0 graus a 360 graus a cada 10 segundos para girar a figura X dentro do bitmap:
public class AnimatedBitmapTilePage : ContentPage
{
···
// For animation
bool isAnimating;
Stopwatch stopwatch = new Stopwatch();
···
protected override void OnAppearing()
{
base.OnAppearing();
isAnimating = true;
stopwatch.Start();
Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
stopwatch.Stop();
isAnimating = false;
}
bool OnTimerTick()
{
const int duration = 10; // seconds
angle = (float)(360f * (stopwatch.Elapsed.TotalSeconds % duration) / duration);
DrawBitmap();
canvasView.InvalidateSurface();
return isAnimating;
}
···
}
Devido à simetria da figura X, isso é o mesmo que girar o angle
valor de 0 graus a 90 graus a cada 2,5 segundos.
O PaintSurface
manipulador cria um sombreador a partir do bitmap e usa o objeto paint para colorir toda a tela:
public class AnimatedBitmapTilePage : ContentPage
{
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Mirror,
SKShaderTileMode.Mirror);
canvas.DrawRect(info.Rect, paint);
}
}
}
As SKShaderTileMode.Mirror
opções garantem que os braços do X em cada bitmap se unam ao X nos bitmaps adjacentes para criar um padrão animado geral que parece muito mais complexo do que a animação simples sugeriria: