跳至正文
首页 » 博客 » Using C#, Xamarin and SkiaSharp to Delight and Amaze (Across Platforms)

Using C#, Xamarin and SkiaSharp to Delight and Amaze (Across Platforms)

什么是SkiaSharp?

跨平台开发可能很棘手,特别是涉及移动平台。你可以通过为每个平台构建一个单独的应用程序来完全避免它,但这既不划算,也不是特别有趣。像Xamarin这样的工具至少可以帮助你在所有重要的平台上使用通用的编程语言,虽然这非常适合帮助你共享应用程序的业务逻辑,但它不会自动使共享UI逻辑变得容易。

Xamarin/Microsoft提供多种工具来帮助跨不同平台共享UI实现。他们创建了Xamarin.Forms ,以帮助您为应用程序抽象地定义UI视图,然后跨支持的平台重用它们。Xamarin.Forms非常酷 ,至少我们是这样认为的,这就是为什么我们有一个可用的新产品 ,以确保您可以使用Xamarin.Forms做更多令人敬畏的事情。但是,如果仍然有一些表格不能做的事情,但你仍然需要呢?在C # 中跨平台渲染自定义图形不是很好吗?

嗯,几年前我就想这么做,但挑战在于当时没有跨平台的C # 2D API可用于所有重要的平台。因此,我围绕各种本地2D渲染api创建了一个抽象,让我一次编写一些渲染逻辑,并让一些抽象层将其转换为对各种本地渲染api的正确调用序列。我发现,虽然,是有很多有趣的开销使用这条路径。我的逻辑将在C # 中构建一些2D图形基元,然后需要在Android上表示为Java对象,然后这些Java对象需要在Android的本机渲染层中表示为本机类,依此类推每个平台。有这么多的抽象层导致一些不小的开销,当你使用一个2D渲染API和复杂的2D图形的chattiness结合起来。

好吧,Xamarin自己必须感受到同样的痛苦,因为它导致他们创建SkiaSharp。SkiaSharp是一个跨平台的2D图形渲染API,您可以通过Xamarin在许多不同的平台上使用,包括Android/iOS平台。SkiaSharp是直接围绕Skia渲染库的C本机API的一些C # 绑定。Skia是一个快速的开源渲染库,在Android和Chrome浏览器以及许多其他高调项目中大量使用。使用SkiaSharp,您可以在许多平台上进行快速的2D图形渲染,而与API的交互开销相对较小,因为C # API能够直接与Skia本机库对话。在本文中,我将引导您了解如何开始使用API。

开始

首先,打开Visual Studio并创建一个新的跨平台项目。这是Xamarin安装到Visual Studio中的模板之一。

根据您的偏好,可以将其设置为Xamarin.Forms应用程序或本机应用程序。我刚刚为这个演示的目的创建了一个本机应用程序,通过PCL (可移植类库) 共享代码,而不是共享项目。这将创建与模板关联的默认平台的主机,并且您可以添加其他项目,前提是它们还支持SkiaSharp。

当我选择一个本机跨平台应用程序时,它创建了一个PCL项目来在平台之间共享代码,一个Xamarin.Android项目和一个Xamarin.iOS项目,但我随后又添加了一个WPF项目和一个UWP项目,因为这两个平台都支持SkiaSharp进行渲染。然后,我将PCL项目的引用添加到我添加到解决方案的两个新项目中。

接下来,您需要向解决方案添加一些NuGet包。如果您右键单击解决方案节点并选择 “管理解决方案的Nuget包”,那么您应该能够在线搜索并将SkiaSharp和SkiaSharp.Views安装到解决方案中的所有项目中。SkiaSharp.Views有一些帮助类,可以帮助您将SkiaSharp的渲染引导到您选择的平台上的本机UI视图中,从而为您节省一些样板逻辑。SkiaSharp.Views应该为除PCL之外的所有项目安装,它不提供任何实用程序 (因为它没有绑定到特定的UI平台)。

我们的目标

我们将从一些简单的东西开始,通过渲染一个简单的圆,然后我们将移动到相当复杂的东西。我们的一个旧的示例浏览器有一个整洁的动画虹膜效果:

这是执行,我收集,通过叠加一堆图像,然后旋转它们在不同的方向。在这种情况下,图像是静态的,但我很好奇,如果你可以,或者,简单地把所有的逻辑到一个视图,并呈现这一切动态。让我们朝着这个目标前进。

我们首先利用SkiaSharp.Views为Android创建一个名为IrisView的组件。我们稍后将进一步扩展此内容,然后填写其他平台的详细信息。

使用系统;
使用系统收藏通用;
使用系统Linq;
使用系统文本;

使用AndroidApp;
使用Android内容;
使用Android操作系统;
使用Android运行时;
使用Android视图;
使用Android小部件;
使用SkiaSharp;
使用AndroidUtil;

命名空间SkiaSharpDemoDroid
{
公共IrisView
: SkiaSharp视图AndroidSKCanvasView
{
私人处理程序_handler;

私人无效Initialize()
{
_handler=新建处理程序(上下文主弯针);
}

公共IrisView (上下文上下文)
:底座(上下文)
{
Initialize();
}

公共IrisView (上下文上下文,IAttributeSetattrs)
:底座(上下文,attrs)
{
Initialize();
}
公共IrisView (上下文上下文,IAttributeSetattrs,智力defStyleAttr)
:底座(上下文,attrs)
{
Initialize();
}<br>受保护IrisView (IntPtrjavaReference,JniHandleOwnership转让)
:底座(javaReference, transfer)
{
Initialize();
}

受保护超控无效OnDraw (SKSurface表面,SKImageInfo信息)
{
底座OnDraw (表面,信息);

// 从skia表面获取画布。
var上下文=表面帆布;

// 清除画布的当前内容。
上下文DrawColor (SKColors透明,SKBlendMode清除);

// 确定圆的中心。
varcenterX=信息宽度/2.0f;
varcenterY=信息高度/2.0f;

// 确定圆的半径。
varrad=数学Min (信息宽度,信息高度)/2.0f;

// 创建paint对象来填充圆。
使用(SKPaintp=新建SKPaint())
{
p等冲程<span style="color:#b4b4b4;">= false;
p.IsAntialias = true;
p.Color = SKColor (25500 ); // 填充圆。

上下文DrawCircle(centerX,centerY,rad,p);
};
}
}
}

此代码的大部分是样板逻辑,用于扩展SkiaSharp.views提供的视图之一,以便能够在普通Android视图中使用SkiaSharp呈现内容。如果我们专注于绘画的逻辑:

// 从skia表面获取画布。
var上下文=表面帆布;

// 清除画布的当前内容。
上下文DrawColor (SKColors透明,SKBlendMode清除);

// 确定圆的中心。
varcenterX=信息宽度/2.0f;
varcenterY=信息高度/2.0f;

// 确定圆的半径。
varrad=数学Min (信息宽度,信息高度)/2.0f;

// 创建paint对象来填充圆。
使用(SKPaintp=新建SKPaint())
{
p等冲程=false;
pIsAntialias=;
p颜色=新建SKColor(255,0,0);
// 填充圆圈。
上下文DrawCircle(centerX, centerY, rad, p);
};

在这里我们:

  • 获取要绘制的Skia画布。
  • 清除画布中显示的初始颜色 (擦除之前的任何渲染)。
  • 确定视图的中心和我们可以绘制的圆的半径。
  • 使用抗锯齿创建一个将用红色填充的Skia Paint对象。
  • 使用配置的paint对象将圆绘制到画布中。

然后,回到我们的主要活动,如果你添加这个IrisView的布局,你应该看到这样的东西:

好,这是整洁的,但是,很明显,如果渲染逻辑在本机Android视图中,我们就不能在平台之间共享它,对吗?所以让我们重构一下。在PCL中,我们创建了一个名为IrisRenderer.cs的类,其中包含以下内容:

使用SkiaSharp的

; 
使用系统;
使用系统收藏泛型;

命名空间SkiaSharpDemo
{
公共IrisRenderer
{
public IrisRenderer()
{

}

private DateTime _lastRender = DateTime.Now;
私有bool _forward = true;
私有double _progress = 0;
private double _duration = 5000
private Random _rand = new Random ();
private
静态立方 (p)
{
返回p * p * p;
}
public static double CubicEase( double t)
{
if (t < .5)
{
var fastTime = t * 2.0

返回 .5* 立方 (fastTime);
}

var outFastTime = (1.0-t) * 2.0;
var y = 1.0立方 (outFastTime);
返回 .5 .5 * y;
}

private bool _first = true;
public void RenderIris( SKSurface surface, SKImageInfo info)
{
if (_first)
{
_first = false;
_lastRender = DateTime.Now;
}
var currTime = DateTime.Now;
var elapsed = (currTime-_lastRender).TotalMilliseconds;

_lastRender = currTime;

if (_forward)
{
_progress = elapsed/_duration;
}
else
{
_progress -= elapsed/_duration;
}
if (_progress > 1.0)
{
& nbsp; _Progress = 1.0;
_forward = false;
_duration = 1000 4000 * _randNextDouble();
}
if (_progress < 0)
{
_progress = 0;
_forward = true;
_duration = 1000 4000 * _rand.NextDouble();
}

var context = surface画布;
上下文DrawColor( SKColors.Transparent, SKBlendMode.Clear);

// 确定圆心.
var centerX = info宽度/2.0f;
变量中心 = 信息Height/2.0f;

// 确定半径的圆。
var rad = 数学Min (信息宽度,信息高度)/2.0f;

var fromR = 255</跨度>;
varfromG=0;
varfromB=0;

vartoR=0;
vartoG=0;
vartoB=255;

var实际进度=立方体 (_progress);
varactualR=(字节)数学圆形 (fromR () (托尔-fromR)*actualProgress);
varactualG=(字节)数学圆形 (fromG () (托格-fromG)*actualProgress);
varactualB=(字节)数学圆形 (fromB ()(toB-fromB)*actualProgress);

// 创建paint对象来填充圆。
使用(SKPaintp=新建SKPaint())
{
p等冲程=false;
pIsAntialias=;
p颜色=新建SKColor(actualR, actualG, actualB);
// 填充圆圈。
上下文DrawCircle(centerX, centerY, rad, p);
};
}
}
}

我们修改了Android的IrisView看起来像这样:

使用系统;
使用系统收藏通用;
使用系统Linq;
使用系统文本;

使用AndroidApp;
使用Android内容;
使用Android操作系统;
使用Android运行时;
使用Android视图;
使用Android小部件;
使用SkiaSharp;
使用AndroidUtil;

命名空间SkiaSharpDemoDroid
{
公共IrisView
: SkiaSharp视图AndroidSKCanvasView
{
私人IrisRenderer_irisRenderer;
私人处理程序_handler;

私人无效Initialize()
{
// IrisRenderer将执行此视图的实际呈现逻辑。
_irisRenderer=新建IrisRenderer();
_handler=新建处理程序(上下文主弯针);
// 这将启动一个tick循环,我们稍后将使用它来制作动画。
_handlerPost (打勾);
}

私人日期时间_最后一次=日期时间现在;
私人无效Tick()
{
日期时间当前时间=日期时间<span style= "颜色: # b4b4b4;">。现在;
// 不要太频繁地渲染新帧。
如果(当前时间-_最后一次<时间跨度从毫秒 (16))
{
_handlerPost (打勾);
返回;
}
_最后一次=当前时间;
无效 ();
_handlerPost (打勾);
}

公共IrisView (上下文上下文)
:底座(上下文)
{
Initialize();
}

公共IrisView (上下文上下文,IAttributeSetattrs)
:底座(上下文,attrs)
{
Initialize();
}
公共IrisView (上下文上下文,IAttributeSetattrs,智力defStyleAttr)
:底座(上下文,attrs)
{
Initialize();
}
受保护IrisView (IntPtrjavaReference,JniHandleOwnership转让)
:底座(javaReference, transfer)
{
Initialize();
}

受保护超控无效OnDraw (SKSurface表面,SKImageInfo信息)
{
基地OnDraw(surface,info);

_irisrendererRenderIris(surface, info);
}
}
}

通过这种方式,我们将所有渲染逻辑分解到位于PCL中的共享类中,该类可以在我们想要定位的所有平台之间共享。我们只需要编码一次,就完成了。此外,我们还添加了一个原始动画系统,该系统将使视图无效并在一定时间间隔内重新绘制,以便我们的渲染器可以使用线性插值 (使用立方缓动功能缓解) 分析经过的时间和动画更改。很酷,是吧?这是在蓝色和红色之间的动画中圆圈的样子:

好,在这一点上,我们可以填写IrisView的其余实现。这些需要是单独的类,因为每个平台在构成UI视图方面都有不同的要求,并且我们可以使用不同的机制来驱动动画循环,但是我们的想法是最小化这些类的内容以仅包含平台特定的行为。我们还可以选择构建额外的抽象 (例如,围绕动画的抽象),这将进一步减少这些类中的逻辑。下面是iOS视图的版本:

使用系统;
使用系统收藏通用;
使用系统Linq;
使用系统文本;

使用基础;
使用SkiaSharp;
使用UIKit;
使用CoreGraphics;
使用CoreFoundation;

命名空间SkiaSharpDemoiOS
{
公共IrisView
: SkiaSharp视图iOSSKCanvasView
{
私人IrisRenderer_irisRenderer;

公共IrisView()
:底座()
{
Initialize();
}
公共IrisView (CGRect框架)
:底座(框架)
{
Initialize();
}
公共IrisView (IntPtrp)
:底座(p)
{
Initialize();
}

私人无效Initialize()
{

背景颜色=UIColor清除;
_irisRenderer=新建IrisRenderer();
DispatchQueue主队列DispatchAsync(Tick);
}

私人日期时间_最后一次=Dat电子时间现在;
私人无效Tick()
{
日期时间当前时间=日期时间现在;
如果(当前时间-_最后一次<时间跨度从毫秒 (16))
{
DispatchQueue主队列DispatchAsync(Tick);
返回;
}
_最后一次=当前时间;
SetNeedsDisplay();
DispatchQueue主队列DispatchAsync(Tick);
}

公共超控无效DrawInSurface (SKSurface表面,SKImageInfo信息)
{
底座DrawInSurface (表面,信息);

varctx=语言学GetCurrentContext();
ctxClearRect (边界);

_irisRendererRenderIris(surface, info);
}
}
}

和WPF:

使用系统;
使用系统收藏通用;
使用系统Linq;
使用系统文本;
使用系统穿线任务;
使用系统窗户;
使用SkiaSharp视图台式机;

命名空间SkiaSharpDemoWPF
{
公共IrisView
: SkiaSharp视图WPFSKElement
{
私人IrisRenderer_irisRenderer;

公共IrisView()
{
Initialize();
}

私人无效Initialize()
{
_irisRenderer=新建IrisRenderer();
任务延迟 (8)ContinueWith((t)=>调度员BeginInvoke (系统窗户穿线DispatcherPriority正常,(行动) 打勾));
}


私人日期时间_最后一次=日期时间现在;
私人无效Tick()
{
日期时间当前时间=日期时间现在;
如果(当前时间-_最后一次<时间跨度右前omMilliseconds (16))
{
任务延迟 (8)ContinueWith((t)=>调度员BeginInvoke (系统窗户穿线DispatcherPriority正常,(行动) 打勾));
返回;
}
_最后一次=当前时间;
InvalidateVisual();
任务延迟 (8)ContinueWith((t)=>调度员BeginInvoke (系统窗户穿线DispatcherPriority正常,(行动) 打勾));
}

受保护超控无效OnPaintSurface (SKPaintSurfaceEventArgse)
{
底座涂漆表面 (e);

_irisRendererRenderIris(e表面,e信息);
}
}
}

And UWP:

使用系统;
使用系统收藏通用;
使用系统Linq;
使用系统文本;
使用系统穿线任务;
使用系统窗户;
使用窗户用户界面核心;
使用SkiaSharp视图UWP;

命名空间SkiaSharpDemoUWP
{
公共IrisView
: SkiaSharp视图UWPSKXamlCanvas
{
私人IrisRenderer_irisRenderer;

公共IrisView()
{
Initialize();
}

私人无效Initialize()
{
_irisRenderer=新建IrisRenderer();
任务延迟 (8)ContinueWith((t)=>调度员RunAsync (CoreDispatcherPriority正常,打勾));
}


私人日期时间_最后一次=日期时间现在;
私人无效Tick()
{
日期时间当前时间=日期时间现在;
如果(当前时间-_最后一次<时间跨度从毫秒 (16))
{
任务延迟 (8)ContinueWith((t)=>调度员RunAsync (CoreDispatcherPriority正常,打勾));
返回;
}
_最后一次=当前时间;
无效 ();
任务延迟 (8)ContinueWith((t)=>调度员RunAsync (CoreDispatcherPriority正常,打勾));
}

受保护超控无效OnPaintSurface (SKPaintSurfaceEventArgse)
{
底座涂漆表面 (e);

_irisRendererRenderIris(e表面,e信息);
}
}
}

现在,我们可以运行这些并观察完全相同的渲染行为!如果你还不明白为什么这太棒了,让我们把事情变得更加复杂,好吗?使用此内容更新您的IrisRenderer:

使用SkiaSharp;
使用系统;
使用系统收藏通用;

命名空间SkiaSharpDemo
{
公共IrisArc
{
公共浮子CenterX {获取;设置;}
公共浮子CenterY {获取;设置;}
公共bool识别方向 {获取;设置;}
公共智力NumLevels {获取;设置;}
公共浮子BaseHue {获取;设置;}
公共浮子基线亮度 {获取;设置;}
公共浮子BaseSaturation {获取;设置;}
公共浮子半径 {获取;设置;}
公共浮子跨度 {获取;设置;}
公共列表<元组<浮子,智力>> 形状 {获取;设置;}
公共浮子MinTransitionLength {获取;设置;}
公共浮子MaxTransitionLength {获取;设置;}
公共浮子旋转角度 {获取;设置;}
公共浮子不透明度 {获取;<span style="color:#569cd6;"> 设置;}
公共bool顺时针方向 {获取;内部设置;}

公共IrisArc()
{
CenterX=.5f;
CenterY=.5f;
认可方向=;
NumLevels=3;
BaseHue=220;
基线度=50;
基本饱和度=50;
半径=.75f;
跨距=.2f;
形状=新建列表<元组<浮子,智力>>();
MinTransitionLength=6;
MaxTransitionLength=10;
旋转角度=0;
不透明度=.8f;
GenerateShape();
}

私人静态随机_兰特=新建随机();

私人无效GenerateShape()
{
浮子电流角=0.0f;
& nbsp; int currentLevel = 1 (int) 数学运算Round(_rand.NextDouble() * this.NumLevels);
float degreeChange = 0.0f;

while (currentAngle = 360)
{
addtofhape (currentAngle,currentLevel);
if (currentAngle >= 360)
{
break;
}
degreeChange = (float) Math.Round(MinTransitionLength _rand.NextDouble() *
MaxTransitionLength);

if (currentAngle degreeChange > 360)
{
degreeChange = 360-currentAngle;
}

currentAngle = currentAngle degreeChange;
}
}
private void addtofshape (float currentAngle, int currentLevel)
{
bool isUp = & nbsp;;
智力变化量;
智力maxLevels=NumLevels 1;

如果(当前级别= =maxLevels)
{
isUp=false;
}
其他
{
如果(_兰特NextDouble()>.5)
{
isUp=false;
}
}

如果(isUp)
{
变化量=(智力)数学圆形 (1.0 _兰特NextDouble()*(maxLevels-当前级别));
当前级别=当前级别 变化量;

如果(当前级别>NumLevels)
{
当前级别=NumLevels;
}
}
其他
{
Changeamunt = (int) 数学Round( 1.0.NextDouble() * (currentLevel-1 ));
currentLevel = currentLevel-changeament;

if (currentLevel < 1)
{
currentLevel = 1
}
}

这一点形状Add( 元组 < floatint >(currentAngle * (float) Math.PI/180.0f ,currentLevel));
}

public void Render( SKSurface surface, SKImageInfo info)
{
float centerX = CenterX;
float centerY = CenterY;
float最小半径 = 半径-跨度/2.0f;
float最大半径 = 半径跨度/2.0f;

var上下文 = 曲面.画布;
centerX = 信息.宽度 * centerX;
centerY <spanstyle = "color:# b4b4b4;">=信息高度*centerY;

浮子rad=(浮子)数学Min (信息宽度,信息高度)/2.0f;

最小半径=最小半径*rad;
最大半径=最大半径*rad;

列表<浮子> 半径=新建列表<浮子> ();
列表<浮子> oldRadii;
元组<浮子,智力> currentItem;
浮子lastAngle;
浮子angleDelta;
智力currentRadius;
浮子电流角;

用于(var=0; i<NumLevels 1; i )
{
半径添加 (minRadius (最大半径-最小半径)*/(NumLevels));
}
如果(!认可方向)
{
oldRadii=半径;
半径=新建列表<浮子> ();
用于(var j = oldradies.Count-1; j >= 0; j --)
{
radies.Add (oldradies [j]);
}
}


上下文Save();
上下文Translate(centerX,centerY);
context旋转度 (旋转角度);
上下文Translate(-centerX,-centerY);

SKPath path = new SKPath ();
SKColor c = SKColorFromHsl(
BaseHue,
BaseSaturation,
BaseLightness,
(byte) Math.Round(Opacity * 255.0 ));
SKPaint p = new SKPaint ();
pIsAntialias = true;
p.IsStroke = false;
pColor = c;


if (!arcognsouward)
{
path.MoveTo (radies [0] centerX,<span style = "color:# b5cea8;">0中心);

SKRect r = SKRect (centerX-半径 [0 ],centerY-半径 [0 ],centerX半径 [0 ],中心半径 [0 ]);

路径ArcTo(r, 360-180false );
路径ArcTo(r, 180-180false );
路径Close();
}

currenttradius = this形状 [0]Item2;
lastAngle = 0;
路径MoveTo (半径 [currentRadius] centerX, 0 centerY);
对于 (var i = 1; i < this.Shape.Count;我)
{
currentItem = this.Shape[i];
currentAngle = currentItem.Item1;
currenttradius = currentItem.Item2;

angleDelta = currentAngle-lastAngle;

路径线条 (
& nbsp; (float )(centerX半径 [currenttradius] * Math.Cos(lastAngle)),
(float )(centerY半径 [currenttradius] * Math.Sin(lastAngle);

SKRect r = SKRect (
centerX-半径 [currenttradius],
centerY-半径 [currenttradius],
centerX半径 [currenttradius],
centerY半径 [currenttradius]);


路径ArcTo(r,
(float )(lastAngle * 180.0/Math.PI),
(float )((currentAngle-lastAngle) * 180.0/Math.PI), false );
lastAngle = currentAngle;
}

if (arecognsouvard)
{
path.Close();
path.MoveTo (半径 [0] centerX, 0 centerY);
SKRect </span> r=新建SKRect(centerX-半径 [0],中心-半径 [0], centerX 半径 [0],中心 半径 [0]);

路径ArcTo(r,360,-180,false);
路径ArcTo(r,180,-180,false);
}
路径关闭 ();

上下文DrawPath(path, p);
路径处置 ();
p处置 ();
上下文恢复 ();
}
}

公共IrisRenderer
{
公共IrisRenderer()
{

}

私人日期时间_拉斯特伦德=日期时间现在;
私人bool_前进=;
私人_进度=0;
私人_持续时间=5000;
私人随机_兰特=新建随机();

私人静态立方 (p)
{
& nbsp; 返回p * p * p;
}
public static double CubicEase( double t)
{
if (t < .5)
{
var fastTime = t * 2.0

返回 .5
* Cubic(fastTime);
}
var outFastTime = (1.0-t) * 2.0
var y = 1.0-立方 (outFastTime);
返回 .5 * y;
}

私有bool _第一个 = true
public void RenderIris( SKSurface surface, SKImageInfo info)
{
if (_first)
{
first = false;
_lastrender = DateTimeNow;
}
var currTime = DateTime.Now;
var elapsed = (currTime-_lastrender)TotalMilliseconds;

_lastRender = currTime;

if (_forward)
{
_progress = elapsed/_duration;
}
else
{
_progress -= elapsed/_duration;
}
if (_progress > 1.0)
{
_progress = 1.0;
_forward = false;
_duration = 1000 4000 * _rand.NextDouble();
}
if (_progress < 0)
{
_progress = 0;
_forward = true
_duration = 1000 4000 * _rand.NextDouble();
}

var context = surface画布;
上下文DrawColor( SKColors.Transparent, <span style = "color:# b8d7a3;">SKBlendMode
清除);

// 确定圆的中心。
varcenterX=信息宽度/2.0f;
varcenterY=信息高度/2.0f;

// 确定圆的半径。
varrad=数学Min (信息宽度,信息高度)/2.0f;

varfromR=255;
varfromG=0;
varfromB=0;

vartoR=0;
vartoG=0;
vartoB=255;

var实际进度=立方体 (_progress);
varactualR=(字节)数学圆形 (fromR () (托尔-fromR)*actualProgress);
varactualG=(字节)数学圆形 (fromG () (托格-fromG)*actualProgress);
varactualB=(<span style= "color:#569cd6;"> 字节)数学圆形 (fromB ()(toB-fromB)*actualProgress);

// 创建paint对象来填充圆。
使用(SKPaintp=新建SKPaint())
{
p等冲程=false;
pIsAntialias=;
p颜色=新建SKColor(actualR、actualG、actualB);
// 填充圆圈。
上下文DrawCircle(centerX, centerY, rad, p);
};
}
}
}

这篇文章中这个逻辑是怎么回事,我就不细说了,但如果有兴趣,我可以在后面的文章中分解一下。尽管如此,我还是通过展示如何跨平台重用大量复杂逻辑来展示这一点。如果我们现在重新运行我们的应用程序,我们应该看到相互啮合的齿轮相互反向旋转的复杂视觉:

这里有一个运动的视频:

现在你相信我这太棒了吗?因此,你可能会想: “格雷厄姆,如果SkiaSharp让跨平台的高性能渲染变得如此容易,如果有人构建了一些我们可以在跨平台应用程序中使用的非常棒的UI东西,那不是很整洁吗?”。嗯,是的,实际上,这就是我们这么做的确切原因:

包扎起来

如果你已经关注Infragistics Xamarin一段时间了,你可能知道我们已经有了一个基于Xamarin的产品 (而且我们现在有一个新版本!)。然而,可能不明显的是,新版本的产品已经过重大重新设计,以便在17.0版本的所有平台之间拥有完全一致的API、性能和行为故事。我们的Xamarin产品的早期版本是我们的原生Android和iOS产品的薄薄的贴面。这是可行的,因为我们的原生移动api彼此足够相似。然而,就最大一致性的拍摄而言,api彼此之间不够一致 (并且对于某些组件完全不同),这使得该策略比期望的更昂贵和限制。那,当我们在被子下工作时,不为人所知的黑魔法,这样你就可以绑定你的。基于NET的数据直接针对我们的 (绝对是非.NET) 本地组件有效,这个东西在幕后非常复杂。

当SkiaSharp出现时,我们知道我们有机会将产品重新设想为C # 产品,甚至 “一直到” 渲染层,并重新调整API (和底层逻辑),使其在Xamarin.Forms,Xamarin.Android,xamarin.iOS,此外,与我们的桌面XAML平台非常相似。除非桌面具有一些独特的WPF功能,否则我们普遍使事情变得非常接近,因此在许多情况下,您可以在平台之间粘贴逻辑,而只需进行较小的调整或不进行调整。最重要的是,当您使用我们新的Xamarin组件时,在大多数情况下,您运行的逻辑与我们流行的桌面WPF产品完全相同。我们为我们所做的工作感到非常自豪,并希望它能让你高兴!让我们知道!

-格雷厄姆 </p