路径信息和枚举Path Information and Enumeration

下载示例下载示例Download Sample Download the sample

获取有关路径的信息和枚举的内容Get information about paths and enumerate the contents

SKPath 类定义一些属性和方法,可用于获取有关路径的信息。The SKPath class defines several properties and methods that allow you to obtain information about the path. Bounds TightBounds 属性 (和相关的方法) 获取路径的度量的维度。The Bounds and TightBounds properties (and related methods) obtain the metrical dimensions of a path. Contains 方法允许您确定某个特定点位于路径中。The Contains method lets you determine if a particular point is within a path.

有时它可用于确定所有的直线和曲线构成路径的总长度。It is sometimes useful to determine the total length of all the lines and curves that make up a path. 计算此长度不是从算法上简单的任务,因此整个类名为 PathMeasure 专用于它。Calculating this length is not an algorithmically simple task, so an entire class named PathMeasure is devoted to it.

还有有时很有用,若要获取所有绘制操作和构成路径的点。It is also sometimes useful to obtain all the drawing operations and points that make up a path. 首先, 此工具似乎不必要:如果程序创建了路径, 则程序已经知道了内容。At first, this facility might seem unnecessary: If your program has created the path, the program already knows the contents. 但是,你已了解路径也可以创建由路径效果并通过转换为路径的文本字符串However, you've seen that paths can also be created by path effects and by converting text strings into paths. 此外可以获取所有绘制操作和组成这些路径的点。You can also obtain all the drawing operations and points that make up these paths. 一种可能性是算法转换应用到所有点,例如,若要使文字环绕半球:One possibility is to apply an algorithmic transform to all the points, for example, to wrap text around a hemisphere:

获取路径长度Getting the Path Length

在本文中路径和文本了解了如何使用 DrawTextOnPath 方法来绘制其基线遵循的路径的文本字符串。In the article Paths and Text you saw how to use the DrawTextOnPath method to draw a text string whose baseline follows the course of a path. 但如果你想要调整文本大小,以便精确适合的路径?But what if you want to size the text so that it fits the path precisely? 圆环绘制文本非常简单,因为圆的周长很容易计算。Drawing text around a circle is easy because the circumference of a circle is simple to calculate. 但的椭圆的周长或贝塞尔曲线的长度并不那么简单。But the circumference of an ellipse or the length of a Bézier curve is not so simple.

SKPathMeasure 类可帮助。The SKPathMeasure class can help. 构造函数接受SKPath自变量,并且 Length 属性将显示其长度。The constructor accepts an SKPath argument, and the Length property reveals its length.

此类进行了演示路径长度示例中,基于贝塞尔曲线页。This class is demonstrated in the Path Length sample, which is based on the Bezier Curve page. PathLengthPage.xaml 文件派生InteractivePage和包括在触控界面:The PathLengthPage.xaml file derives from InteractivePage and includes a touch interface:

<local:InteractivePage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:local="clr-namespace:SkiaSharpFormsDemos"
                       xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
                       xmlns:tt="clr-namespace:TouchTracking"
                       x:Class="SkiaSharpFormsDemos.Curves.PathLengthPage"
                       Title="Path Length">
    <Grid BackgroundColor="White">
        <skia:SKCanvasView x:Name="canvasView"
                           PaintSurface="OnCanvasViewPaintSurface" />
        <Grid.Effects>
            <tt:TouchEffect Capture="True"
                            TouchAction="OnTouchEffectAction" />
        </Grid.Effects>
    </Grid>
</local:InteractivePage>

PathLengthPage.xaml.cs 代码隐藏文件,可将四个触摸点,以定义终结点和三次方贝塞尔曲线的控制点。The PathLengthPage.xaml.cs code-behind file allows you to move four touch points to define the end points and control points of a cubic Bézier curve. 三个字段定义文本字符串,SKPaint对象和文本的计算所得的宽度:Three fields define a text string, an SKPaint object, and a calculated width of the text:

public partial class PathLengthPage : InteractivePage
{
    const string text = "Compute length of path";

    static SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Black,
        TextSize = 10,
    };

    static readonly float baseTextWidth = textPaint.MeasureText(text);
    ...
}

baseTextWidth字段是基于文本的宽度TextSize设置为 10。The baseTextWidth field is the width of the text based on a TextSize setting of 10.

PaintSurface处理程序绘制贝塞尔曲线,然后调整大小以适合其整个长度的文本:The PaintSurface handler draws the Bézier curve and then sizes the text to fit along its full length:

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

    canvas.Clear();

    // Draw path with cubic Bezier curve
    using (SKPath path = new SKPath())
    {
        path.MoveTo(touchPoints[0].Center);
        path.CubicTo(touchPoints[1].Center,
                     touchPoints[2].Center,
                     touchPoints[3].Center);

        canvas.DrawPath(path, strokePaint);

        // Get path length
        SKPathMeasure pathMeasure = new SKPathMeasure(path, false, 1);

        // Find new text size
        textPaint.TextSize = pathMeasure.Length / baseTextWidth * 10;

        // Draw text on path
        canvas.DrawTextOnPath(text, path, 0, 0, textPaint);
    }
    ...
}

Length新创建的属性SKPathMeasure对象获取路径的长度。The Length property of the newly created SKPathMeasure object obtains the length of the path. 路径长度除以baseTextWidth值 (这是基于文本大小为 10 的文本的宽度),然后再乘以 10 的基础文本大小。The path length is divided by the baseTextWidth value (which is the width of the text based on a text size of 10) and then multiplied by the base text size of 10. 结果是显示的文本沿该路径的新文本大小:The result is a new text size for displaying the text along that path:

贝塞尔曲线获取较长或更短,您可以看到更改文本大小。As the Bézier curve gets longer or shorter, you can see the text size change.

遍历路径Traversing the Path

SKPathMeasure 可以实现不止是度量值的路径的长度。SKPathMeasure can do more than just measure the length of the path. 对于任何介于零和路径长度SKPathMeasure对象可以在该点获取路径,然后为路径曲线的正切值的位置。For any value between zero and the path length, an SKPathMeasure object can obtain the position on the path, and the tangent to the path curve at that point. 正切值是可作为一个向量中的窗体SKPoint对象,或者作为对旋转封装在SKMatrix对象。The tangent is available as a vector in the form of an SKPoint object, or as a rotation encapsulated in an SKMatrix object. 此处列出的方法SKPathMeasure的各不相同且灵活的方式获取此信息:Here are the methods of SKPathMeasure that obtain this information in varied and flexible ways:

Boolean GetPosition (Single distance, out SKPoint position)

Boolean GetTangent (Single distance, out SKPoint tangent)

Boolean GetPositionAndTangent (Single distance, out SKPoint position, out SKPoint tangent)

Boolean GetMatrix (Single distance, out SKMatrix matrix, SKPathMeasureMatrixFlags flag)

成员 SKPathMeasureMatrixFlags 枚举是:The members of the SKPathMeasureMatrixFlags enumeration are:

  • GetPosition
  • GetTangent
  • GetPositionAndTangent

脚踏车半管道页之间进行动画处理看起来沿三次方贝塞尔曲线来回骑脚踏车上一个简图:The Unicycle Half-Pipe page animates a stick figure on a unicycle that seems to ride back and forth along a cubic Bézier curve:

用于对半管道和 unicycle 进行描边的UnicycleHalfPipePage 对象定义为类中的字段。SKPaintThe SKPaint object used for stroking both the half-pipe and the unicycle is defined as a field in the UnicycleHalfPipePage class. 此外定义是SKPath脚踏车对象:Also defined is the SKPath object for the unicycle:

public class UnicycleHalfPipePage : ContentPage
{
    ...
    SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = 3,
        Color = SKColors.Black
    };

    SKPath unicyclePath = SKPath.ParseSvgPathData(
        "M 0 0" +
        "A 25 25 0 0 0 0 -50" +
        "A 25 25 0 0 0 0 0 Z" +
        "M 0 -25 L 0 -100" +
        "A 15 15 0 0 0 0 -130" +
        "A 15 15 0 0 0 0 -100 Z" +
        "M -25 -85 L 25 -85");
    ...
}

此类包含的标准重写之一OnAppearingOnDisappearing动画的方法。The class contains the standard overrides of the OnAppearing and OnDisappearing methods for animation. PaintSurface处理程序创建后半部分管道的路径,然后绘制它。The PaintSurface handler creates the path for the half-pipe and then draws it. SKPathMeasure然后创建对象,根据此路径:An SKPathMeasure object is then created based on this path:

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

        canvas.Clear();

        using (SKPath pipePath = new SKPath())
        {
            pipePath.MoveTo(50, 50);
            pipePath.CubicTo(0, 1.25f * info.Height,
                             info.Width - 0, 1.25f * info.Height,
                             info.Width - 50, 50);

            canvas.DrawPath(pipePath, strokePaint);

            using (SKPathMeasure pathMeasure = new SKPathMeasure(pipePath))
            {
                float length = pathMeasure.Length;

                // Animate t from 0 to 1 every three seconds
                TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
                float t = (float)(timeSpan.TotalSeconds % 5 / 5);

                // t from 0 to 1 to 0 but slower at beginning and end
                t = (float)((1 - Math.Cos(t * 2 * Math.PI)) / 2);

                SKMatrix matrix;
                pathMeasure.GetMatrix(t * length, out matrix,
                                      SKPathMeasureMatrixFlags.GetPositionAndTangent);

                canvas.SetMatrix(matrix);
                canvas.DrawPath(unicyclePath, strokePaint);
            }
        }
    }
}

PaintSurface处理程序计算的值t,是由从 0 到 1 每隔 5 秒。The PaintSurface handler calculates a value of t that goes from 0 to 1 every five seconds. 然后,它使用Math.Cos函数以将其转换为值t,范围从 0 到 1 并返回到 0,其中 0 对应于马车开头在左上角,而 1 对应于马车右上角。It then uses the Math.Cos function to convert that to a value of t that ranges from 0 to 1 and back to 0, where 0 corresponds to the unicycle at the beginning on the top left, while 1 corresponds to the unicycle at the top right. 余弦函数会导致要慢的放在管道的顶部和底部最快的速度。The cosine function causes the speed to be slowest at the top of the pipe and fastest at the bottom.

请注意,此值的t必须乘以的第一个参数的路径长度GetMatrixNotice that this value of t must be multiplied by the path length for the first argument to GetMatrix. 矩阵然后应用到SKCanvas对象,用于绘制的马车路径。The matrix is then applied to the SKCanvas object for drawing the unicycle path.

枚举路径Enumerating the Path

两个嵌入的类SKPath允许您枚举路径的内容。Two embedded classes of SKPath allow you to enumerate the contents of path. 这些类是 SKPath.Iterator SKPath.RawIterator These classes are SKPath.Iterator and SKPath.RawIterator. 两个类是非常相似,但SKPath.Iterator可以消除与长度为零,或接近零长度的路径中的元素。The two classes are very similar, but SKPath.Iterator can eliminate elements in the path with a zero length, or close to a zero length. RawIterator下面的示例中使用。The RawIterator is used in the example below.

你可以获取类型的对象SKPath.RawIterator通过调用 CreateRawIterator 方法SKPathYou can obtain an object of type SKPath.RawIterator by calling the CreateRawIterator method of SKPath. 枚举通过的路径通过重复调用来实现 Next 方法。Enumerating through the path is accomplished by repeatedly calling the Next method. 将四个数组传递给它SKPoint值:Pass to it an array of four SKPoint values:

SKPoint[] points = new SKPoint[4];
...
SKPathVerb pathVerb = rawIterator.Next(points);

Next方法返回的成员 SKPathVerb 枚举类型。The Next method returns a member of the SKPathVerb enumeration type. 这些值表示特定的绘图命令的路径中。These values indicate the particular drawing command in the path. 插入数组中的有效点数目取决于此谓词:The number of valid points inserted in the array depends on this verb:

  • Move 使用单个点Move with a single point
  • Line 使用两个点Line with two points
  • Cubic 具有四个点Cubic with four points
  • Quad 使用三个点Quad with three points
  • Conic 使用三个点 (,调用 ConicWeight 的权重的方法)Conic with three points (and also call the ConicWeight method for the weight)
  • Close 使用一个点Close with one point
  • Done

Done谓词指示路径枚举已完成。The Done verb indicates that the path enumeration is complete.

请注意,有没有Arc谓词。Notice that there are no Arc verbs. 这表示所有弧线将都转换为贝塞尔曲线时添加到的路径。This indicates that all arcs are converted into Bézier curves when added to the path.

中的信息的一些SKPoint是冗余的数组。Some of the information in the SKPoint array is redundant. 例如,如果Move谓词后跟Line谓词,则伴随的两个点的第一个Line等同于Move点。For example, if a Move verb is followed by a Line verb, then the first of the two points that accompany the Line is the same as the Move point. 在实践中,此冗余是非常有帮助。In practice, this redundancy is very helpful. 时你会获得Cubic谓词,它都伴有定义三次方贝塞尔曲线的所有四个点。When you get a Cubic verb, it is accompanied by all four points that define the cubic Bézier curve. 不需要保留当前的位置由上一个谓词。You don't need to retain the current position established by the previous verb.

但是,是有问题的谓词, CloseThe problematic verb, however, is Close. 此命令之前由建立轮廓线的开头从当前位置绘制一条直线Move命令。This command draws a straight line from the current position to the beginning of the contour established earlier by the Move command. 理想情况下,Close谓词应提供这两个点而不是只是一个点。Ideally, the Close verb should provide these two points rather than just one point. 更糟的是在于点附带Close谓词始终是 (0,0)。What's worse is that the point accompanying the Close verb is always (0, 0). 当您通过路径进行枚举时,您可能需要保留Move点和当前的位置。When you enumerate through a path, you'll probably need to retain the Move point and the current position.

枚举、 平展和 MalformingEnumerating, Flattening, and Malforming

属性有时更加理想应用算法转换到错误的路径以某种方式:It is sometimes desirable to apply an algorithmic transform to a path to malform it in some way:

大多数这些字母包含的直线,但这些直线具有显然已篡改成曲线。Most of these letters consist of straight lines, yet these straight lines have apparently been twisted into curves. 这是如何实现?How is this possible?

关键是原始的直线被拆分为一系列的较小的直线。The key is that the original straight lines are broken into a series of smaller straight lines. 然后可以以不同方式形成一条曲线操作这些单独的较小直线。These individual smaller straight lines can then be manipulated in different ways to form a curve.

若要使用此过程,帮助 SkiaSharpFormsDemos 示例包含一个静态 PathExtensions Interpolate分解的方法到大量较短的行的长度只能有一个单元的直线。To help with this process, the SkiaSharpFormsDemos sample contains a static PathExtensions class with an Interpolate method that breaks down a straight line into numerous short lines that are only one unit in length. 此外,该类包含三种类型的贝塞尔曲线转换为一系列的近似曲线的小直线的几种方法。In addition, the class contains several methods that convert the three types of Bézier curves into a series of tiny straight lines that approximate the curve. (在本文中介绍了参数化公式三种类型的贝塞尔曲线。)此过程称为_平展_曲线:(The parametric formulas were presented in the article Three Types of Bézier Curves.) This process is called flattening the curve:

static class PathExtensions
{
    ...
    static SKPoint[] Interpolate(SKPoint pt0, SKPoint pt1)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * pt0.X + t * pt1.X;
            float y = (1 - t) * pt0.Y + t * pt1.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenCubic(SKPoint pt0, SKPoint pt1, SKPoint pt2, SKPoint pt3)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2) + Length(pt2, pt3));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * (1 - t) * (1 - t) * pt0.X +
                        3 * t * (1 - t) * (1 - t) * pt1.X +
                        3 * t * t * (1 - t) * pt2.X +
                        t * t * t * pt3.X;
            float y = (1 - t) * (1 - t) * (1 - t) * pt0.Y +
                        3 * t * (1 - t) * (1 - t) * pt1.Y +
                        3 * t * t * (1 - t) * pt2.Y +
                        t * t * t * pt3.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenQuadratic(SKPoint pt0, SKPoint pt1, SKPoint pt2)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * (1 - t) * pt0.X + 2 * t * (1 - t) * pt1.X + t * t * pt2.X;
            float y = (1 - t) * (1 - t) * pt0.Y + 2 * t * (1 - t) * pt1.Y + t * t * pt2.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenConic(SKPoint pt0, SKPoint pt1, SKPoint pt2, float weight)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float denominator = (1 - t) * (1 - t) + 2 * weight * t * (1 - t) + t * t;
            float x = (1 - t) * (1 - t) * pt0.X + 2 * weight * t * (1 - t) * pt1.X + t * t * pt2.X;
            float y = (1 - t) * (1 - t) * pt0.Y + 2 * weight * t * (1 - t) * pt1.Y + t * t * pt2.Y;
            x /= denominator;
            y /= denominator;
        }

        return points;
    }

    static double Length(SKPoint pt0, SKPoint pt1)
    {
        return Math.Sqrt(Math.Pow(pt1.X - pt0.X, 2) + Math.Pow(pt1.Y - pt0.Y, 2));
    }
}

从扩展方法中引用所有这些方法CloneWithTransform还包含在此类中,如下所示。All these methods are referenced from the extension method CloneWithTransform also included in this class and shown below. 此方法通过枚举路径命令和构造基于的数据的新路径克隆一个路径。This method clones a path by enumerating the path commands and constructing a new path based on the data. 但是,新路径仅包含MoveToLineTo调用。However, the new path consists only of MoveTo and LineTo calls. 所有曲线和直线被都减少到一系列小的行。All the curves and straight lines are reduced to a series of tiny lines.

调用时CloneWithTransform,传递给方法Func<SKPoint, SKPoint>,它是使用函数SKPaint参数,它返回SKPoint值。When calling CloneWithTransform, you pass to the method a Func<SKPoint, SKPoint>, which is a function with an SKPaint parameter that returns an SKPoint value. 每个点来应用自定义算法转换为调用此函数:This function is called for every point to apply a custom algorithmic transform:

static class PathExtensions
{
    public static SKPath CloneWithTransform(this SKPath pathIn, Func<SKPoint, SKPoint> transform)
    {
        SKPath pathOut = new SKPath();

        using (SKPath.RawIterator iterator = pathIn.CreateRawIterator())
        {
            SKPoint[] points = new SKPoint[4];
            SKPathVerb pathVerb = SKPathVerb.Move;
            SKPoint firstPoint = new SKPoint();
            SKPoint lastPoint = new SKPoint();

            while ((pathVerb = iterator.Next(points)) != SKPathVerb.Done)
            {
                switch (pathVerb)
                {
                    case SKPathVerb.Move:
                        pathOut.MoveTo(transform(points[0]));
                        firstPoint = lastPoint = points[0];
                        break;

                    case SKPathVerb.Line:
                        SKPoint[] linePoints = Interpolate(points[0], points[1]);

                        foreach (SKPoint pt in linePoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[1];
                        break;

                    case SKPathVerb.Cubic:
                        SKPoint[] cubicPoints = FlattenCubic(points[0], points[1], points[2], points[3]);

                        foreach (SKPoint pt in cubicPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[3];
                        break;

                    case SKPathVerb.Quad:
                        SKPoint[] quadPoints = FlattenQuadratic(points[0], points[1], points[2]);

                        foreach (SKPoint pt in quadPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[2];
                        break;

                    case SKPathVerb.Conic:
                        SKPoint[] conicPoints = FlattenConic(points[0], points[1], points[2], iterator.ConicWeight());

                        foreach (SKPoint pt in conicPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[2];
                        break;

                    case SKPathVerb.Close:
                        SKPoint[] closePoints = Interpolate(lastPoint, firstPoint);

                        foreach (SKPoint pt in closePoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        firstPoint = lastPoint = new SKPoint(0, 0);
                        pathOut.Close();
                        break;
                }
            }
        }
        return pathOut;
    }
    ...
}

克隆的路径减少到很小的直线,因为转换函数将已转换为曲线的直线,直线的功能。Because the cloned path is reduced to tiny straight lines, the transform function has the capability of converting straight lines to curves.

请注意,该方法将调用在变量中的每个分布的第一个点保留firstPoint并在每个当前位置在变量中绘制命令lastPointNotice that the method retains the first point of each contour in the variable called firstPoint and the current position after each drawing command in the variable lastPoint. 这些变量是必需构造最终关闭的行时Close遇到谓词。These variables are necessary to construct the final closing line when a Close verb is encountered.

GlobularText示例使用此扩展方法似乎是围绕半球文本换行中的三维效果:The GlobularText sample uses this extension method to seemingly wrap text around a hemisphere in a 3D effect:

GlobularTextPage 类构造函数执行此转换。The GlobularTextPage class constructor performs this transform. 它会创建SKPaint对象的文本,然后获取SKPath对象从GetTextPath方法。It creates an SKPaint object for the text, and then obtains an SKPath object from the GetTextPath method. 这是传递到的路径CloneWithTransform扩展方法以及转换函数:This is the path passed to the CloneWithTransform extension method along with a transform function:

public class GlobularTextPage : ContentPage
{
    SKPath globePath;

    public GlobularTextPage()
    {
        Title = "Globular Text";

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

        using (SKPaint textPaint = new SKPaint())
        {
            textPaint.Typeface = SKTypeface.FromFamilyName("Times New Roman");
            textPaint.TextSize = 100;

            using (SKPath textPath = textPaint.GetTextPath("HELLO", 0, 0))
            {
                SKRect textPathBounds;
                textPath.GetBounds(out textPathBounds);

                globePath = textPath.CloneWithTransform((SKPoint pt) =>
                {
                    double longitude = (Math.PI / textPathBounds.Width) *
                                            (pt.X - textPathBounds.Left) - Math.PI / 2;
                    double latitude = (Math.PI / textPathBounds.Height) *
                                            (pt.Y - textPathBounds.Top) - Math.PI / 2;

                    longitude *= 0.75;
                    latitude *= 0.75;

                    float x = (float)(Math.Cos(latitude) * Math.Sin(longitude));
                    float y = (float)Math.Sin(latitude);

                    return new SKPoint(x, y);
                });
            }
        }
    }
    ...
}

转换函数首先计算两个名为的值longitudelatitude,范围从-π/2 的顶部和文本,左侧到 π/2 的右侧和底部的文本。The transform function first calculates two values named longitude and latitude that range from –π/2 at the top and left of the text, to π/2 at the right and bottom of the text. 这些值的范围不直观地令人满意,因此它们减少乘以 0.75。The range of these values isn't visually satisfactory, so they are reduced by multiplying by 0.75. (请尝试不使用这些调整代码。(Try the code without those adjustments. 将文本转换成过于隐蔽在北极和南极,并在两侧不可过于省略。)这些三维的球面坐标将转换为二维xy由标准公式的坐标。The text becomes too obscure at the north and south poles, and too thin at the sides.) These three-dimensional spherical coordinates are converted to two-dimensional x and y coordinates by standard formulas.

作为字段存储的新路径。The new path is stored as a field. PaintSurface处理程序然后只是需要中心和缩放要在屏幕上显示的路径:The PaintSurface handler then merely needs to center and scale the path to display it on the screen:

public class GlobularTextPage : ContentPage
{
    SKPath globePath;
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint pathPaint = new SKPaint())
        {
            pathPaint.Style = SKPaintStyle.Fill;
            pathPaint.Color = SKColors.Blue;
            pathPaint.StrokeWidth = 3;
            pathPaint.IsAntialias = true;

            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(0.45f * Math.Min(info.Width, info.Height));     // radius
            canvas.DrawPath(globePath, pathPaint);
        }
    }
}

这是非常通用技术。This is a very versatile technique. 如果路径效果的数组中所述路径效果文章非常未涵盖一些你认为应将包括在内,这是一种方法来填充空白。If the array of path effects described in the Path Effects article doesn't quite encompass something you felt should be included, this is a way to fill in the gaps.