如前两篇文章所示,该 SKShader
类可以创建线性或圆形渐变。 本文重点介绍使用位图平铺区域的 SKShader
对象。 位图可以水平和垂直重复,无论是在其原始方向上还是水平和垂直翻转。 翻转可避免图块之间的不连续:
创建此着色器的静态 SKShader.CreateBitmap
方法具有 SKBitmap
参数和 SKShaderTileMode
枚举的两个成员:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)
这两个参数指示用于水平平铺和垂直平铺的模式。 这是与渐变方法一起使用的相同 SKShaderTileMode
枚举。
CreateBitmap
重载包括用于对平铺位图执行转换的 SKMatrix
参数:
public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)
本文包含将此矩阵转换与平铺位图一起使用的几个示例。
浏览图块模式
示例的“着色器和其他效果”页的“位图平铺”部分中的第一个程序演示了两个 SKShaderTileMode
参数的效果。 位图图块翻转模式 XAML 文件实例化一个 SKCanvasView
和两个 Picker
视图,使你可以为水平平铺和垂直平铺选择 SKShaderTilerMode
值。 请注意,SKShaderTileMode
成员的数组在 Resources
节中定义:
<?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>
代码隐藏文件的构造函数在位图资源中加载,其中显示了一个猴子坐着。 它首先使用 SKBitmap
的 ExtractSubset
方法裁剪图像,以便头部和脚触摸位图的边缘。 然后,构造函数使用 Resize
方法创建另一个大小为一半的位图。 这些更改使位图更适合平铺:
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);
}
}
}
PaintSurface
处理程序从两个 Picker
视图获取 SKShaderTileMode
设置,并根据位图和这两个值创建 SKShader
对象。 此着色器用于填充画布:
左侧的 iOS 屏幕显示 SKShaderTileMode.Clamp
的默认值的效果。 位图位于左上角。 在位图下方,像素的下行一直重复。 在位图右侧,最右侧的像素列一直重复。 画布的其余部分由位图右下角的深棕色像素着色。 很明显,Clamp
选项几乎永远不会用于位图平铺!
中心的 Android 屏幕显示两个参数的 SKShaderTileMode.Repeat
结果。 图块水平和垂直重复。 通用 Windows 平台屏幕显示 SKShaderTileMode.Mirror
。 图块重复,但可横向和垂直翻转。 此选项的优点是图块之间没有不连续。
请记住,可以将不同的选项用于水平和垂直重复。 可以将 SKShaderTileMode.Mirror
指定为 CreateBitmap
的第二个参数,但 SKShaderTileMode.Repeat
为第三个参数。 每一行,猴子仍然在正常图像和镜子图像之间交替,但没有猴子是倒置的。
图案背景
位图平铺通常用于从相对较小的位图创建图案背景。 经典示例是砖墙。
“算法砖墙”页创建一个小位图,似于一整块砖和被灰泥隔开的两半砖。 由于此砖块也用于下一个示例,因此它由静态构造函数创建,并使用静态属性公开:
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; }
···
}
生成的位图宽 70 像素,高 60 像素:
“算法砖墙”页的其余部分创建一个 SKShader
对象,该对象水平和垂直重复此图像:
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);
}
}
}
结果如下:
你可能更喜欢更现实的东西。 在这种情况下,你可以拍摄实际砖墙的照片,然后裁剪它。 此位图宽 300 像素,高 150 像素:
此位图用于“摄影砖墙”页:
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);
}
}
}
请注意,CreateBitmap
的 SKShaderTileMode
参数都是 Mirror
。 使用从真实图像创建的图块时,通常需要使用此选项。 镜像图块可避免中断:
需要执行一些工作才能获取图块的合适位图。 这一块效果不好,因为更深的砖块太突出了。 它经常出现在重复的图像中,揭示了这个砖墙是从较小的位图构造的。
示例的 Media 文件夹还包括石墙的此图像:
但是,原始位图对于图块来说稍大一点。 它可以调整大小,但 SKShader.CreateBitmap
方法也可以通过向其应用转换来调整图块的大小。 “石墙”页中演示了此选项:
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);
}
}
}
创建 SKMatrix
值,将图像缩放为原始大小的一半:
转换是否对 CreateBitmap
方法中使用的原始位图进行操作? 或者,它是否会转换图块的结果数组?
回答此问题的一种简单方法是在转换过程中包括旋转:
SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));
如果转换应用于单个图块,则应旋转图块的每个重复图像,结果将包含许多不连续。 但从此屏幕截图中很明显,图块的复合数组已转换:
该示例基于此 240 像素方形位图使用位图平铺模拟木纹背景:
这是一张木质地板的照片。 SKShaderTileMode.Mirror
选项允许它显示为更大的木材区域:
图块对齐
到目前为止显示的所有示例都使用 SKShader.CreateBitmap
创建的着色器覆盖整个画布。 在大多数情况下,你将使用位图平铺来归档较小的区域,或者(更很少)用于填充粗线的内部。 下面是用于较小矩形的摄影砖墙图块:
你可能觉得这很好,也可能觉得不好。 也许你感到不安的是,平铺图案不以矩形左上角的全砖开头。 这是因为着色器与画布对齐,而不是它们装饰的图形对象。
修复很简单。 基于平移转换创建 SKMatrix
值。 该转换有效地将平铺图案移动到希望平铺左上角对齐的点。 此方法在“图块对齐”页中演示,该页创建了上面所示未对齐图块的图像:
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);
}
}
}
“图块对齐”页包含一个 TapGestureRecognizer
。 点击或单击屏幕,程序使用 SKMatrix
参数切换到 SKShader.CreateBitmap
方法。 此转换将移动模式,使左上角包含一个完整的砖块:
还可以使用此技术确保平铺位图图案在其绘制区域内居中。 在“居中图块”页中,PaintSurface
处理程序首先计算坐标,就好像要在画布中心显示单个位图一样。 然后,它使用这些坐标为 SKShader.CreateBitmap
创建平移转换。 此转换会移动整个模式,使图块居中:
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);
}
}
}
PaintSurface
处理程序通过在画布中心绘制一个圆来结束。 果然,其中一个图块正好位于圆中心,其他图块采用对称模式排列:
另一种居中方法实际上要容易一点。 你可以将平铺图案的角居中,而不是构建平铺转换。 在 SKMatrix.MakeTranslation
调用中,对画布中心使用参数:
SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);
模式仍居中且对称,但中心中没有图块:
通过旋转简化
有时在 SKShader.CreateBitmap
方法中使用旋转转换可以简化位图图块。 当尝试为链链接围栏定义图块时,这变得很明显。 ChainLinkTile.cs 文件创建此处显示的图块(为了清楚起见,背景为粉红色):
图块需要包含两个链接,以便代码将图块划分为四个象限。 左上角和右下象限相同,但它们不完整。 电线上有一些小凹槽,必须在右上角和左下角的象限内再绘制一些。 执行所有这些工作的文件长度为 174 行。
事实证明,创建此图块要容易得多:
如果位图平铺着色器旋转 90 度,则视觉对象几乎相同。
创建更简单的链环图块的代码是“链环图块”页的一部分。 构造函数根据程序正在运行的设备类型确定图块大小,然后调用 CreateChainLinkTile
,该类型使用线条、路径和渐变着色器在位图上绘制:
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;
}
}
···
}
除了电线外,图块是透明的,这意味着你可以在其他内容上显示它。 程序加载其中一个位图资源,显示它以填充画布,然后在顶部绘制着色器:
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);
}
}
}
请注意,着色器旋转 45 度,因此其方向类似于真正的链环围栏:
对位图图块进行动画处理
可以通过对矩阵转换进行动画处理,对整个位图图块模式进行动画处理。 也许你希望模式水平或垂直移动或同时移动。 为此,可以根据移动坐标创建一个平移变换。
还可以在小位图上绘制,或者以每秒 60 次的速度操作位图的像素位。 然后,该位图可用于平铺,整个平铺模式似乎可以进行动画处理。
“动画位图图块”页演示了此方法。 位图将实例化为 64 像素正方形的字段。 构造函数调用 DrawBitmap
,使其具有初始外观。 如果 angle
字段为零(就像首次调用方法时一样),则位图包含两行作为 X 交叉。无论 angle
值如何,行都足够长,始终到达位图边缘:
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);
}
}
···
}
动画开销发生在 OnAppearing
中,且 OnDisappearing
会替代。 OnTimerTick
方法每 10 秒将 angle
值从 0 度设置为 360 度,以旋转位图中的 X 图:
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;
}
···
}
由于 X 图的对称性,这与每 2.5 秒将 angle
值从 0 度旋转到 90 度相同。
PaintSurface
处理程序从位图创建着色器,并使用画图对象为整个画布着色:
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);
}
}
}
SKShaderTileMode.Mirror
选项可确保每个位图中的 X 的臂与相邻位图中的 X 联接,以创建看起来比简单动画更复杂的整体动画模式: