Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Objevte různé efekty cesty, které umožňují používat cesty pro rozprašování a vyplňování.
Účinek cesty je instance SKPathEffect třídy, která je vytvořena s jednou z osmi statických metod vytváření definovaných třídou. Objekt SKPathEffect se pak nastaví na PathEffect vlastnost objektu SKPaint pro řadu zajímavých efektů, například stroking čáry s malou replikovanou cestou:

Efekty cesty umožňují:
- Tah na čáru s tečkami a pomlčkami
- Tah na čáru s libovolnou vyplněnou cestou
- Vyplnění oblasti čárami šrafování
- Vyplnění oblasti dlaždicovou cestou
- Zaoblení ostrých rohů
- Přidání náhodného "zpoždění" do čar a křivek
Kromě toho můžete zkombinovat dva nebo více efektů cesty.
Tento článek také ukazuje, jak použít metodu GetFillPathSKPaint převodu jedné cesty na jinou cestu použitím vlastností SKPaint, včetně StrokeWidth a PathEffect. Výsledkem jsou některé zajímavé techniky, například získání cesty, která je osnovou jiné cesty. GetFillPath je také užitečný v souvislosti s efekty cesty.
Tečky a čárky
Použití PathEffect.CreateDash metody bylo popsáno v článku Tečky a Pomlčky. Prvním argumentem metody je matice obsahující sudé číslo dvou nebo více hodnot, které se střídají mezi délkami pomlček a délkami mezer mezi pomlčkami:
public static SKPathEffect CreateDash (Single[] intervals, Single phase)
Tyto hodnoty nejsou relativní vzhledem k šířce tahu. Pokud je například šířka tahu 10 a chcete mít čáru složenou z čtvercových pomlček a čtvercových mezer, nastavte intervals matici na { 10, 10 }. Argument phase označuje, kde v pomlčkovém vzoru začíná čára. Pokud v tomto příkladu chcete, aby čára začínala čtvercovou mezerou, nastavte phase na hodnotu 10.
Konce pomlček jsou ovlivněny StrokeCap vlastností SKPaint. U širokých šířek tahů je velmi běžné nastavit tuto vlastnost tak, aby SKStrokeCap.Round zaokrouhlila konce pomlček. V tomto případě hodnoty v intervals matici nezahrnují dodatečnou délku vyplývající z zaokrouhlení. To znamená, že kruhová tečka vyžaduje zadání šířky nuly. Pro šířku tahu 10 vytvořte čáru s kruhovými tečkami a mezerami mezi tečkami stejného průměru, použijte intervals matici { 0, 20 }.
Animovaný tečkovaný text stránka je podobná stránce osnovy textu popsané v článku Integrace textu a grafiky v tom, že zobrazuje obrysové textové znaky nastavením Style vlastnosti objektu SKPaint na SKPaintStyle.Stroke. Animovaný tečkovaný text navíc používá SKPathEffect.CreateDash k tomu, aby byl obrys tečkovaný vzhled, a program také animuje phase argument SKPathEffect.CreateDash metody, aby se tečky zdály pohybovat kolem textových znaků. Tady je stránka v režimu na šířku:
Třída AnimatedDottedTextPage začíná definováním některých konstant a také přepíše OnAppearing a OnDisappearing metody animace:
public class AnimatedDottedTextPage : ContentPage
{
const string text = "DOTTED";
const float strokeWidth = 10;
static readonly float[] dashArray = { 0, 2 * strokeWidth };
SKCanvasView canvasView;
bool pageIsActive;
public AnimatedDottedTextPage()
{
Title = "Animated Dotted Text";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
Device.StartTimer(TimeSpan.FromSeconds(1f / 60), () =>
{
canvasView.InvalidateSurface();
return pageIsActive;
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
pageIsActive = false;
}
...
}
Obslužná rutina PaintSurface začíná vytvořením objektu SKPaint pro zobrazení textu. Vlastnost TextSize se upraví na základě šířky obrazovky:
public class AnimatedDottedTextPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create an SKPaint object to display the text
using (SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue,
})
{
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
// Calculate offsets to center the text on the screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
// Animate the phase; t is 0 to 1 every second
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 1 / 1);
float phase = -t * 2 * strokeWidth;
// Create dotted line effect based on dash array and phase
using (SKPathEffect dashEffect = SKPathEffect.CreateDash(dashArray, phase))
{
// Set it to the paint object
textPaint.PathEffect = dashEffect;
// And draw the text
canvas.DrawText(text, xText, yText, textPaint);
}
}
}
}
Na konci metody SKPathEffect.CreateDash je volána metoda, dashArray která je definována jako pole a animovaná phase hodnota. Instance SKPathEffect je nastavena na PathEffect vlastnost SKPaint objektu pro zobrazení textu.
Případně můžete objekt nastavit SKPathEffect na SKPaint objekt před měřením textu a jeho středem na stránce. V takovém případě však animované tečky a pomlčky způsobují určité variace velikosti vykresleného textu a text má tendenci trochu vibrovat. (Vyzkoušejte to!)
Všimněte si také, že jako animované tečky krouží kolem textových znaků, je v každé uzavřené křivce určitý bod, ve kterém se zdá, že tečky se zdají vyskočit a nevypadají. Tady cesta, která definuje obrys znaku, začíná a končí. Pokud délka cesty není celočíselným násobem délky pomlčky (v tomto případě 20 pixelů), může se na konec cesty vejít pouze část tohoto vzorku.
Délku pomlčky je možné upravit tak, aby odpovídala délce cesty, ale to vyžaduje určení délky cesty, techniky, která je popsaná v článku Informace o cestě a výčtu.
Program Dot / Dash Morph animuje samotný vzor pomlčky, takže se zdají být pomlčky rozdělené na tečky, které znovu tvoří pomlčky:
Třída DotDashMorphPage přepíše OnAppearing metody a OnDisappearing metody stejně jako předchozí program, ale třída definuje SKPaint objekt jako pole:
public class DotDashMorphPage : ContentPage
{
const float strokeWidth = 30;
static readonly float[] dashArray = new float[4];
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint ellipsePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create elliptical path
using (SKPath ellipsePath = new SKPath())
{
ellipsePath.AddOval(new SKRect(50, 50, info.Width - 50, info.Height - 50));
// Create animated path effect
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 3 / 3);
float phase = 0;
if (t < 0.25f) // 1, 0, 1, 2 --> 0, 2, 0, 2
{
float tsub = 4 * t;
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2 * tsub;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2;
}
else if (t < 0.5f) // 0, 2, 0, 2 --> 1, 2, 1, 0
{
float tsub = 4 * (t - 0.25f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2 * (1 - tsub);
phase = strokeWidth * tsub;
}
else if (t < 0.75f) // 1, 2, 1, 0 --> 0, 2, 0, 2
{
float tsub = 4 * (t - 0.5f);
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2 * tsub;
phase = strokeWidth * (1 - tsub);
}
else // 0, 2, 0, 2 --> 1, 0, 1, 2
{
float tsub = 4 * (t - 0.75f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2 * (1 - tsub);
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2;
}
using (SKPathEffect pathEffect = SKPathEffect.CreateDash(dashArray, phase))
{
ellipsePaint.PathEffect = pathEffect;
canvas.DrawPath(ellipsePath, ellipsePaint);
}
}
}
}
Obslužná PaintSurface rutina vytvoří eliptickou cestu na základě velikosti stránky a spustí dlouhý oddíl kódu, který nastaví dashArray a phase proměnné. Vzhledem k tomu, že animované proměnné t se pohybuje od 0 do 1, if bloky rozdělují tento čas do čtyř čtvrtletí a v každém z těchto čtvrtletí tsub se také pohybuje od 0 do 1. Na konci program vytvoří SKPathEffect a nastaví ho na SKPaint objekt pro kreslení.
Z cesty k cestě
Metoda GetFillPath přemění SKPaint jednu cestu na jinou na základě nastavení v objektu SKPaint . Pokud chcete zjistit, jak to funguje, nahraďte canvas.DrawPath volání v předchozím programu následujícím kódem:
SKPath newPath = new SKPath();
bool fill = ellipsePaint.GetFillPath(ellipsePath, newPath);
SKPaint newPaint = new SKPaint
{
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);
V tomto novém kódu GetFillPath volání převede ellipsePath (což je pouze ovál) na newPath, který se pak zobrazí s newPaint. Objekt newPaint je vytvořen se všemi výchozími nastaveními vlastností s tím rozdílem, že Style vlastnost je nastavena na základě logické návratové hodnoty z GetFillPath.
Vizuály jsou shodné s výjimkou barvy, která je nastavena ellipsePaint , ale nikoli newPaint. Místo jednoduchých tří teček definovaných v ellipsePath, newPath obsahuje mnoho obrysů cest, které definují řadu teček a pomlček. Jedná se o výsledek použití různých vlastností ellipsePaint (konkrétně StrokeWidth, StrokeCap, , a PathEffect) na ellipsePath a umístění výsledné cesty do newPath. Metoda GetFillPath vrátí logickou hodnotu označující, zda má být vyplněna cílová cesta; v tomto příkladu je návratová hodnota true pro vyplnění cesty.
Zkuste změnit Style nastavení newPaint na SKPaintStyle.Stroke a uvidíte obrysy jednotlivých cest s čárou s šířkou jednoho pixelu.
Stroking with a Path
Metoda SKPathEffect.Create1DPath je koncepčně podobná SKPathEffect.CreateDash s tím rozdílem, že místo vzoru pomlček a mezer zadáte cestu. Tato cesta se několikrát replikuje tak, aby tahovala čáru nebo křivku.
Syntaxe je:
public static SKPathEffect Create1DPath (SKPath path, Single advance,
Single phase, SKPath1DPathEffectStyle style)
Obecně platí, že cesta, na Create1DPath kterou přejdete, bude malá a zacentrovaná kolem bodu (0, 0). Parametr advance označuje vzdálenost mezi středy cesty, protože cesta se replikuje na řádku. Tento argument obvykle nastavíte na přibližnou šířku cesty. Argument phase zde hraje stejnou roli jako v CreateDash metodě.
Členové SKPath1DPathEffectStyle mají tři členy:
TranslateRotateMorph
Člen Translate způsobí, že cesta zůstane ve stejné orientaci jako replikovaná podél čáry nebo křivky. Pro Rotate, cesta je otočena na základě tangens na křivku. Cesta má normální orientaci pro vodorovné čáry. Morph je podobný s Rotate tím rozdílem, že samotná cesta je také zakřivená tak, aby odpovídala zakřivení čáry, která je tahována.
Stránka efektu cesty 1D ukazuje tyto tři možnosti. Soubor OneDimensionalPathEffectPage.xaml definuje výběr obsahující tři položky odpovídající třem členům výčtu:
<?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.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.OneDimensionalPathEffectPage"
Title="1D Path Effect">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Picker x:Name="effectStylePicker"
Title="Effect Style"
Grid.Row="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Translate</x:String>
<x:String>Rotate</x:String>
<x:String>Morph</x:String>
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface"
Grid.Row="1" />
</Grid>
</ContentPage>
Soubor OneDimensionalPathEffectPage.xaml.cs kódu definuje tři SKPathEffect objekty jako pole. Všechny tyto objekty jsou vytvořeny pomocí SKPathEffect.Create1DPathSKPath objektů vytvořených pomocí SKPath.ParseSvgPathData. První je jednoduché pole, druhý je kosočtverec a třetí je obdélník. Používají se k předvedení tří stylů efektů:
public partial class OneDimensionalPathEffectPage : ContentPage
{
SKPathEffect translatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 -10 L 10 -10, 10 10, -10 10 Z"),
24, 0, SKPath1DPathEffectStyle.Translate);
SKPathEffect rotatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 0 L 0 -10, 10 0, 0 10 Z"),
20, 0, SKPath1DPathEffectStyle.Rotate);
SKPathEffect morphPathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -25 -10 L 25 -10, 25 10, -25 10 Z"),
55, 0, SKPath1DPathEffectStyle.Morph);
SKPaint pathPaint = new SKPaint
{
Color = SKColors.Blue
};
public OneDimensionalPathEffectPage()
{
InitializeComponent();
}
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
if (canvasView != null)
{
canvasView.InvalidateSurface();
}
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath path = new SKPath())
{
path.MoveTo(new SKPoint(0, 0));
path.CubicTo(new SKPoint(2 * info.Width, info.Height),
new SKPoint(-info.Width, info.Height),
new SKPoint(info.Width, 0));
switch ((string)effectStylePicker.SelectedItem))
{
case "Translate":
pathPaint.PathEffect = translatePathEffect;
break;
case "Rotate":
pathPaint.PathEffect = rotatePathEffect;
break;
case "Morph":
pathPaint.PathEffect = morphPathEffect;
break;
}
canvas.DrawPath(path, pathPaint);
}
}
}
Obslužná PaintSurface rutina vytvoří Bézierovou křivku, která se kolem sebe smyčuje, a přistupuje k výběru, aby bylo možné určit, který PathEffect by měl být použit k tahu. Tři možnosti – , TranslateRotatea Morph – jsou zobrazeny zleva doprava:
Cesta zadaná v SKPathEffect.Create1DPath metodě je vždy vyplněna. Cesta zadaná v DrawPath metodě je vždy tahu, pokud SKPaint má objekt jeho PathEffect vlastnost nastavena na 1D path efekt. Všimněte si, že pathPaint objekt nemá žádné Style nastavení, které obvykle výchozí Fillnastavení , ale cesta je tahem bez ohledu na to.
Pole použité v příkladu Translate je čtvercové 20 pixelů a advance argument je nastaven na 24. Tento rozdíl způsobuje mezeru mezi rámečky, když je čára zhruba vodorovná nebo svislá, ale pole se trochu překrývají, když je čára diagonální, protože diagonální rámeček je 28,3 pixelů.
Kosočtverec v příkladu Rotate je také široký 20 pixelů. Nastaví advance se na 20, aby body pokračovaly v dotyku, protože kosočtverec je otočen spolu s zakřivení čáry.
Obdélníkový obrazec v příkladu Morph je široký 50 pixelů s advance nastavením 55, aby se mezi obdélníky při zatáhnutí kolem Bézierovy křivky udělala malá mezera.
advance Pokud je argument menší než velikost cesty, replikované cesty se můžou překrývat. To může vést k nějakým zajímavým efektům. Stránka Propojený řetězec zobrazuje řadu překrývajících se kruhů, které se zdá podobat propojenému řetězci, který visí v charakteristickém tvaru katény:
Podívejte se velmi blízko a uvidíte, že ty nejsou ve skutečnosti kruhy. Každá vazba v řetězu je dva oblouky, velikost a umístění, takže se zdá, že se spojují s spojovacími spoji.
Řetěz nebo kabel s jednotným rozdělením hmotnosti se zavěsí ve formě katény. Oblouk postavený ve formě obrácené katenary přináší výhody rovnoměrného rozdělení tlaku z hmotnosti oblouku. Catenary má zdánlivě jednoduchý matematický popis:
y = a · cosh(x / a)
Cosh je hyperbolická kosinusová funkce. Pro x rovnající se 0 je cosh nula a y se rovná hodnotě a. To je střed catenary. Podobně jako kosinusová funkce se říká, že cosh je sudý, což znamená, že cosh(–x) se rovná cosh(x) a zvýšení hodnot pro zvýšení kladných nebo záporných argumentů. Tyto hodnoty popisují křivky, které tvoří strany katény.
Nalezení správné hodnoty pro přizpůsobení catenary na rozměry stránky telefonu není přímý výpočet. Pokud je šířka a výška obdélníku w a h, bude optimální hodnota rovnice shodná s následující rovnicí:
cosh(w / 2 / a) = 1 + h / a
Následující metoda ve LinkedChainPage třídě zahrnuje rovnost odkazem na dva výrazy vlevo a vpravo od rovnítko jako left a right. U malých hodnot a je větší než right; pro velké hodnoty a, left je menší než right. left Smyčka while se zúží na optimální hodnotu:
float FindOptimumA(float width, float height)
{
Func<float, float> left = (float a) => (float)Math.Cosh(width / 2 / a);
Func<float, float> right = (float a) => 1 + height / a;
float gtA = 1; // starting value for left > right
float ltA = 10000; // starting value for left < right
while (Math.Abs(gtA - ltA) > 0.1f)
{
float avgA = (gtA + ltA) / 2;
if (left(avgA) < right(avgA))
{
ltA = avgA;
}
else
{
gtA = avgA;
}
}
return (gtA + ltA) / 2;
}
Objekt SKPath pro odkazy je vytvořen v konstruktoru třídy a výsledný SKPathEffect objekt je pak nastaven na PathEffect vlastnost objektu SKPaint , který je uložen jako pole:
public class LinkedChainPage : ContentPage
{
const float linkRadius = 30;
const float linkThickness = 5;
Func<float, float, float> catenary = (float a, float x) => (float)(a * Math.Cosh(x / a));
SKPaint linksPaint = new SKPaint
{
Color = SKColors.Silver
};
public LinkedChainPage()
{
Title = "Linked Chain";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the individual links
SKRect outer = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
SKRect inner = outer;
inner.Inflate(-linkThickness, -linkThickness);
using (SKPath linkPath = new SKPath())
{
linkPath.AddArc(outer, 55, 160);
linkPath.ArcTo(inner, 215, -160, false);
linkPath.Close();
linkPath.AddArc(outer, 235, 160);
linkPath.ArcTo(inner, 395, -160, false);
linkPath.Close();
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(linkPath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
}
}
...
}
Hlavní úlohou PaintSurface obslužné rutiny je vytvoření cesty pro samotnou catenary. Jakmile určíte optimální hodnotu a uložíte ji do optA proměnné, musí také vypočítat posun od horní části okna. Pak může nashromáždět kolekci SKPoint hodnot pro catenary, převést ji na cestu a nakreslit cestu s dříve vytvořeným SKPaint objektem:
public class LinkedChainPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Black);
// Width and height of catenary
int width = info.Width;
float height = info.Height - linkRadius;
// Find the optimum 'a' for this width and height
float optA = FindOptimumA(width, height);
// Calculate the vertical offset for that value of 'a'
float yOffset = catenary(optA, -width / 2);
// Create a path for the catenary
SKPoint[] points = new SKPoint[width];
for (int x = 0; x < width; x++)
{
points[x] = new SKPoint(x, yOffset - catenary(optA, x - width / 2));
}
using (SKPath path = new SKPath())
{
path.AddPoly(points, false);
// And render that path with the linksPaint object
canvas.DrawPath(path, linksPaint);
}
}
...
}
Tento program definuje cestu použitou Create1DPath k tomu, aby měla bod (0, 0) ve středu. Zdá se to rozumné, protože bod cesty (0, 0) je zarovnaný s čárou nebo křivkou, která je ozdobou. Pro některé speciální efekty ale můžete použít necentrovaný bod (0, 0).
Stránka dopravníkového pásu vytvoří cestu s obdélným dopravníkovým pásem se zakřiveným horním a dolním pásem, který má velikost pro rozměry okna. Tato cesta je tahaná jednoduchým SKPaint objektem o šířce 20 pixelů a barevně šedá a potom se znovu tahá jiným SKPaint objektem s objektem SKPathEffect odkazujícím na cestu, která se podobá malé kbelíku:
Bod cesty kbelíku (0, 0) je úchyt, takže když phase je argument animovaný, kbelíky se zdá být otočené kolem dopravníkového pásu, možná vysunout vodu v dolní části a vysypnout ho v horní části.
Třída ConveyorBeltPage implementuje animaci s přepsáními a OnDisappearing metodamiOnAppearing. Cesta kontejneru je definována v konstruktoru stránky:
public class ConveyorBeltPage : ContentPage
{
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint conveyerPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 20,
Color = SKColors.DarkGray
};
SKPath bucketPath = new SKPath();
SKPaint bucketsPaint = new SKPaint
{
Color = SKColors.BurlyWood,
};
public ConveyorBeltPage()
{
Title = "Conveyor Belt";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the bucket starting with the handle
bucketPath.AddRect(new SKRect(-5, -3, 25, 3));
// Sides
bucketPath.AddRoundedRect(new SKRect(25, -19, 27, 18), 10, 10,
SKPathDirection.CounterClockwise);
bucketPath.AddRoundedRect(new SKRect(63, -19, 65, 18), 10, 10,
SKPathDirection.CounterClockwise);
// Five slats
for (int i = 0; i < 5; i++)
{
bucketPath.MoveTo(25, -19 + 8 * i);
bucketPath.LineTo(25, -13 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 65, -13 + 8 * i);
bucketPath.LineTo(65, -19 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 25, -19 + 8 * i);
bucketPath.Close();
}
// Arc to suggest the hidden side
bucketPath.MoveTo(25, -17);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 65, -17);
bucketPath.LineTo(65, -19);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 25, -19);
bucketPath.Close();
// Make it a little bigger and correct the orientation
bucketPath.Transform(SKMatrix.MakeScale(-2, 2));
bucketPath.Transform(SKMatrix.MakeRotationDegrees(90));
}
...
Kód pro vytvoření kbelíku se dokončí dvěma transformacemi, které kontejner trochu zvětší a otočí ho do stran. Použití těchto transformací bylo jednodušší než úprava všech souřadnic v předchozím kódu.
Obslužná rutina PaintSurface začíná definováním cesty pro samotný dopravníkový pás. Jedná se jednoduše o dvojici čar a dvojici polokroužků, které jsou nakresleny 20 pixelů tmavě šedou čárou:
public class ConveyorBeltPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float width = info.Width / 3;
float verticalMargin = width / 2 + 150;
using (SKPath conveyerPath = new SKPath())
{
// Straight verticals capped by semicircles on top and bottom
conveyerPath.MoveTo(width, verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, 2 * width, verticalMargin);
conveyerPath.LineTo(2 * width, info.Height - verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, width, info.Height - verticalMargin);
conveyerPath.Close();
// Draw the conveyor belt itself
canvas.DrawPath(conveyerPath, conveyerPaint);
// Calculate spacing based on length of conveyer path
float length = 2 * (info.Height - 2 * verticalMargin) +
2 * ((float)Math.PI * width / 2);
// Value will be somewhere around 200
float spacing = length / (float)Math.Round(length / 200);
// Now animate the phase; t is 0 to 1 every 2 seconds
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 2 / 2);
float phase = -t * spacing;
// Create the buckets PathEffect
using (SKPathEffect bucketsPathEffect =
SKPathEffect.Create1DPath(bucketPath, spacing, phase,
SKPath1DPathEffectStyle.Rotate))
{
// Set it to the Paint object and draw the path again
bucketsPaint.PathEffect = bucketsPathEffect;
canvas.DrawPath(conveyerPath, bucketsPaint);
}
}
}
}
Logika pro kreslení dopravníkového pásu nefunguje v režimu na šířku.
Kbelíky by měly být rozsaděny o velikosti přibližně 200 pixelů na dopravníkovém pásu. Dopravníkový pás však pravděpodobně není násobkem 200 pixelů dlouhý, což znamená, že jako phase argument SKPathEffect.Create1DPath je animovaný, kbelíky se objeví a přestanou existovat.
Z tohoto důvodu program nejprve vypočítá hodnotu s názvem length , která je délkou dopravníkového pásu. Vzhledem k tomu, že dopravníkový pás se skládá z rovných čar a polokroužků, jedná se o jednoduchý výpočet. V dalším kroku se počet kbelíků vypočítá vydělením length číslem 200. Zaokrouhlí se na nejbližší celé číslo a toto číslo se pak rozdělí na length. Výsledkem je mezera pro celočíselný počet kbelíků. Argument phase je prostě zlomek.
Z cesty k cestě znovu
V dolní části obslužné rutiny v dopravníkovém pásu DrawSurfacezakomentujte canvas.DrawPath volání a nahraďte ho následujícím kódem:
SKPath newPath = new SKPath();
bool fill = bucketsPaint.GetFillPath(conveyerPath, newPath);
SKPaint newPaint = new SKPaint
{
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);
Stejně jako v předchozím příkladu GetFillPathuvidíte, že výsledky jsou stejné s výjimkou barvy. Po spuštění GetFillPathnewPath objekt obsahuje více kopií cesty kbelíku, přičemž každý umístěný na stejném místě, kde je animace umístila v době volání.
Šrafování oblasti
Metoda SKPathEffect.Create2DLines vyplní oblast paralelními spojnicemi, často označovanými jako šrafování. Metoda má následující syntaxi:
public static SKPathEffect Create2DLine (Single width, SKMatrix matrix)
Argument width určuje šířku tahu čar šrafování. Parametr matrix je kombinací škálování a volitelné rotace. Koeficient měřítka označuje přírůstek pixelu, který Skia používá k rozmístění šrafovaných čar. Rozdělení mezi řádky je měřítko faktoru minus width argument. Pokud je měřítko menší než nebo rovno hodnotě width , mezi čarami šrafování nebude mezera a oblast se zobrazí jako vyplněná. Zadejte stejnou hodnotu pro horizontální a vertikální škálování.
Ve výchozím nastavení jsou šrafované čáry vodorovné. matrix Pokud parametr obsahuje otočení, čáry šrafování jsou otočené po směru hodinových ručiček.
Stránka Výplň hatch ukazuje tento efekt cesty. Třída HatchFillPage definuje tři efekty cesty jako pole, první pro vodorovné šrafovací čáry s šířkou 3 pixely s měřítkem označující, že jsou od sebe rozloženy 6 pixelů. Oddělení mezi čarami je proto tři pixely. Druhý efekt cesty je určen pro svislé šrafované čáry s šířkou šesti pixelů rozmístěnými 24 pixely (takže oddělení je 18 pixelů) a třetí je pro diagonální šrafování 12 pixelů široké mezery 36 pixelů od sebe.
public class HatchFillPage : ContentPage
{
SKPaint fillPaint = new SKPaint();
SKPathEffect horzLinesPath = SKPathEffect.Create2DLine(3, SKMatrix.MakeScale(6, 6));
SKPathEffect vertLinesPath = SKPathEffect.Create2DLine(6,
Multiply(SKMatrix.MakeRotationDegrees(90), SKMatrix.MakeScale(24, 24)));
SKPathEffect diagLinesPath = SKPathEffect.Create2DLine(12,
Multiply(SKMatrix.MakeScale(36, 36), SKMatrix.MakeRotationDegrees(45)));
SKPaint strokePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
Color = SKColors.Black
};
...
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
{
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
}
}
Všimněte si metody matice Multiply . Vzhledem k tomu, že vodorovné a svislé faktory škálování jsou stejné, nezáleží na pořadí, ve kterém jsou násobené matice měřítka a otočení.
Obslužná rutina PaintSurface používá tyto tři efekty cesty se třemi různými barvami v kombinaci fillPaint s vyplněním zaoblené velikosti obdélníku tak, aby odpovídala stránce. Vlastnost nastavená Style na fillPaint hodnotu je ignorována; pokud SKPaint objekt obsahuje efekt cesty vytvořený z SKPathEffect.Create2DLine, oblast je vyplněna bez ohledu na:
public class HatchFillPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath roundRectPath = new SKPath())
{
// Create a path
roundRectPath.AddRoundedRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50), 100, 100);
// Horizontal hatch marks
fillPaint.PathEffect = horzLinesPath;
fillPaint.Color = SKColors.Red;
canvas.DrawPath(roundRectPath, fillPaint);
// Vertical hatch marks
fillPaint.PathEffect = vertLinesPath;
fillPaint.Color = SKColors.Blue;
canvas.DrawPath(roundRectPath, fillPaint);
// Diagonal hatch marks -- use clipping
fillPaint.PathEffect = diagLinesPath;
fillPaint.Color = SKColors.Green;
canvas.Save();
canvas.ClipPath(roundRectPath);
canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), fillPaint);
canvas.Restore();
// Outline the path
canvas.DrawPath(roundRectPath, strokePaint);
}
}
...
}
Pokud se podíváte pečlivě na výsledky, uvidíte, že červené a modré šrafování nejsou omezeny přesně na zaoblený obdélník. (Toto je zřejmě charakteristika základního kódu Skia.) Pokud je to neuspokojivé, zobrazí se alternativní přístup pro šikmé čáry šrafování zeleně: Zaoblený obdélník se použije jako cesta výřezu a čáry šrafování se vykreslují na celou stránku.
Obslužná PaintSurface rutina končí voláním jednoduchého tahu na zaoblený obdélník, abyste viděli nesrovnalosti s červenou a modrou čárou:
Obrazovka Androidu ve skutečnosti nevypadá takto: Měřítko snímku obrazovky způsobilo, že tenké červené čáry a tenké mezery se sloučit do zdánlivě širších červených čar a širších prostorů.
Vyplnění cestou
Tato SKPathEffect.Create2DPath možnost umožňuje vyplnit oblast cestou, která se replikuje vodorovně a svisle, a to v důsledku toho, že oblast provádá:
public static SKPathEffect Create2DPath (SKMatrix matrix, SKPath path)
SKMatrix Faktory škálování označují vodorovné a svislé mezery replikované cesty. Pomocí tohoto matrix argumentu ale nemůžete cestu otočit. Pokud chcete cestu otočit, otočte cestu samotnou pomocí Transform metody definované metodou SKPath.
Replikovaná cesta je obvykle zarovnaná s levými a horními okraji obrazovky, nikoli s vyplněnou oblastí. Toto chování můžete přepsat tak, že mezi 0 a faktory škálování zadáte vodorovné a svislé posuny z levé a horní strany.
Stránka Výplň dlaždice cesty ukazuje tento efekt cesty. Cesta použitá k provazování oblasti je definována jako pole ve PathTileFillPage třídě. Vodorovné a svislé souřadnice jsou v rozsahu –40 až 40, což znamená, že tato cesta je 80 pixelů čtverce:
public class PathTileFillPage : ContentPage
{
SKPath tilePath = SKPath.ParseSvgPathData(
"M -20 -20 L 2 -20, 2 -40, 18 -40, 18 -20, 40 -20, " +
"40 -12, 20 -12, 20 12, 40 12, 40 40, 22 40, 22 20, " +
"-2 20, -2 40, -20 40, -20 8, -40 8, -40 -8, -20 -8 Z");
...
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.Color = SKColors.Red;
using (SKPathEffect pathEffect =
SKPathEffect.Create2DPath(SKMatrix.MakeScale(64, 64), tilePath))
{
paint.PathEffect = pathEffect;
canvas.DrawRoundRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50),
100, 100, paint);
}
}
}
}
Volání v obslužné rutině PaintSurfaceSKPathEffect.Create2DPath nastaví vodorovné a svislé mezery na 64, aby se čtvercové dlaždice s 80 pixely překrývaly. Naštěstí cesta vypadá jako puzzle kus, který pěkně splete s vedlejšími dlaždicemi:
Škálování z původního snímku obrazovky způsobuje určité zkreslení, zejména na obrazovce Androidu.
Všimněte si, že tyto dlaždice se vždy zobrazují jako celek a nikdy se nezkrátí. Na prvních dvou snímcích obrazovky není ani zřejmé, že vyplněná oblast je zaoblený obdélník. Pokud chcete tyto dlaždice zkrátit na konkrétní oblast, použijte cestu výřezu.
Zkuste nastavit Style vlastnost objektu SKPaint na Strokehodnotu a zobrazí se jednotlivé dlaždice, které jsou místo vyplněné.
Je také možné vyplnit oblast dlaždicovým rastrovým obrázkem, jak je znázorněno v článku SkiaSharp rastrové dlaždice.
Zaoblení ostrých rohů
Zaoblený heptagonový program prezentovaný třemi způsoby kreslení oblouku použil tangens oblouk k zakřivení bodů sedmistranné postavy. Stránka Another Rounded Heptagon zobrazuje mnohem jednodušší přístup, který používá efekt cesty vytvořený z SKPathEffect.CreateCorner metody:
public static SKPathEffect CreateCorner (Single radius)
I když je jeden argument pojmenovaný radius, musíte ho nastavit na polovinu požadovaného poloměru rohu. (Toto je charakteristika základního kódu Skia.)
Tady je obslužná rutina PaintSurface třídy AnotherRoundedHeptagonPage :
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
int numVertices = 7;
float radius = 0.45f * Math.Min(info.Width, info.Height);
SKPoint[] vertices = new SKPoint[numVertices];
double vertexAngle = -0.5f * Math.PI; // straight up
// Coordinates of the vertices of the polygon
for (int vertex = 0; vertex < numVertices; vertex++)
{
vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),
radius * (float)Math.Sin(vertexAngle));
vertexAngle += 2 * Math.PI / numVertices;
}
float cornerRadius = 100;
// Create the path
using (SKPath path = new SKPath())
{
path.AddPoly(vertices, true);
// Render the path in the center of the screen
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = 10;
// Set argument to half the desired corner radius!
paint.PathEffect = SKPathEffect.CreateCorner(cornerRadius / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawPath(path, paint);
// Uncomment DrawCircle call to verify corner radius
float offset = cornerRadius / (float)Math.Sin(Math.PI * (numVertices - 2) / numVertices / 2);
paint.Color = SKColors.Green;
// canvas.DrawCircle(vertices[0].X, vertices[0].Y + offset, cornerRadius, paint);
}
}
}
Tento efekt můžete použít buď sstrokingem, nebo vyplněním na Style základě vlastnosti objektu SKPaint . Tady je spuštěný:
Uvidíte, že tato zaoblená heptagon je shodná s dřívějším programem. Pokud potřebujete přesvědčivější, že rohový poloměr je skutečně 100 místo 50 zadaných v SKPathEffect.CreateCorner volání, můžete odkomentovat konečný příkaz v programu a zobrazit kruh 100 poloměru superpozice v rohu.
Náhodné zachytání
Někdy bezchybné rovné čáry počítačové grafiky nejsou úplně to, co chcete, a trochu náhodnosti je žádoucí. V takovém případě budete chtít vyzkoušet metodu SKPathEffect.CreateDiscrete :
public static SKPathEffect CreateDiscrete (Single segLength, Single deviation, UInt32 seedAssist)
Tento efekt cesty můžete použít buď pro stroking, nebo vyplňování. Čáry jsou odděleny do propojených segmentů – přibližnou délku, kterou určuje segLength – a rozšiřují se v různých směrech. Rozsah odchylky od původní čáry je určen deviation.
Posledním argumentem je počáteční hodnota použitá k vygenerování pseudonáhodné sekvence použité pro efekt. Efekt jitter bude vypadat trochu jinak pro různá semena. Argument má výchozí hodnotu nula, což znamená, že efekt je stejný při každém spuštění programu. Pokud chcete, aby se při každém překreslení obrazovky různě zatěžovala, můžete nastavit počáteční hodnotu na Millisecond vlastnost DataTime.Now hodnoty (například).
Stránka Jitter Experiment umožňuje experimentovat s různými hodnotami při ladění obdélníku:
Program je jednoduchý. Soubor JitterExperimentPage.xaml vytvoří instanci dvou Slider prvků a :SKCanvasView
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.JitterExperimentPage"
Title="Jitter Experiment">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Slider">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Slider x:Name="segLengthSlider"
Grid.Row="0"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference segLengthSlider},
Path=Value,
StringFormat='Segment Length = {0:F0}'}"
Grid.Row="1" />
<Slider x:Name="deviationSlider"
Grid.Row="2"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference deviationSlider},
Path=Value,
StringFormat='Deviation = {0:F0}'}"
Grid.Row="3" />
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="4"
PaintSurface="OnCanvasViewPaintSurface" />
</Grid>
</ContentPage>
Obslužná rutina PaintSurface v souboru JitterExperimentPage.xaml.cs kódu se volá při každé Slider změně hodnoty. SKPathEffect.CreateDiscrete Volá pomocí těchto dvou Slider hodnot a používá je k tahu obdélníku:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float segLength = (float)segLengthSlider.Value;
float deviation = (float)deviationSlider.Value;
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = 5;
paint.Color = SKColors.Blue;
using (SKPathEffect pathEffect = SKPathEffect.CreateDiscrete(segLength, deviation))
{
paint.PathEffect = pathEffect;
SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100);
canvas.DrawRect(rect, paint);
}
}
}
Tento efekt můžete použít i pro vyplnění, v takovém případě je obrys vyplněné oblasti předmětem těchto náhodných odchylek. Stránka Jitter Text ukazuje použití tohoto efektu cesty k zobrazení textu. Většina kódu v PaintSurface obslužné rutině JitterTextPage třídy je věnována nastavení velikosti a zacentrování textu:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
string text = "FUZZY";
using (SKPaint textPaint = new SKPaint())
{
textPaint.Color = SKColors.Purple;
textPaint.PathEffect = SKPathEffect.CreateDiscrete(3f, 10f);
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
// Calculate offsets to center the text on the screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
canvas.DrawText(text, xText, yText, textPaint);
}
}
Tady běží v režimu na šířku:
Osnova cesty
Už jste viděli dva malé příklady GetFillPath metody SKPaint, která existuje dvě verze:
public Boolean GetFillPath (SKPath src, SKPath dst, Single resScale = 1)
public Boolean GetFillPath (SKPath src, SKPath dst, SKRect cullRect, Single resScale = 1)
Jsou vyžadovány pouze první dva argumenty. Metoda přistupuje k cestě odkazované argumentem src , upraví data cesty na základě vlastností tahu v objektu SKPaint (včetně PathEffect vlastnosti) a pak zapíše výsledky do dst cesty. Parametr resScale umožňuje snížit přesnost a vytvořit menší cílovou cestu a cullRect argument může eliminovat obrysy mimo obdélník.
Jedno základní použití této metody nezahrnuje účinky cesty vůbec: Pokud SKPaint má objekt jeho Style vlastnost nastavena na SKPaintStyle.Stroke, a nemá jeho PathEffect sadu, pak GetFillPath vytvoří cestu, která představuje obrys zdrojové cesty, jako by byl tahaný vlastnostmi malování.
Pokud src je například cesta jednoduchým kruhem poloměru 500 a SKPaint objekt určuje šířku tahu 100, dst pak se cesta změní na dva soustředné kruhy, jeden s poloměrem 450 a druhý s poloměrem 550. Volá se GetFillPath metoda, protože vyplnění této dst cesty je stejné jako prostroking src cesty. Můžete ale také pohánět dst cestu, abyste viděli obrysy cesty.
Klepnutím nastíníte cestu, která ukazuje tuto cestu . Vytvoří instanci SKCanvasView v souboru TapToOutlineThePathPage.xaml.TapGestureRecognizer Soubor TapToOutlineThePathPage.xaml.cs kódem definuje tři SKPaint objekty jako pole, dva pro stroking s šířkami tahů 100 a 20 a třetí pro vyplňování:
public partial class TapToOutlineThePathPage : ContentPage
{
bool outlineThePath = false;
SKPaint redThickStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 100
};
SKPaint redThinStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 20
};
SKPaint blueFill = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Blue
};
public TapToOutlineThePathPage()
{
InitializeComponent();
}
void OnCanvasViewTapped(object sender, EventArgs args)
{
outlineThePath ^= true;
(sender as SKCanvasView).InvalidateSurface();
}
...
}
Pokud se obrazovka nepoklepla, PaintSurface obslužná rutina použije blueFill k vykreslení kruhové cesty objekty a redThickStroke malování:
public partial class TapToOutlineThePathPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath circlePath = new SKPath())
{
circlePath.AddCircle(info.Width / 2, info.Height / 2,
Math.Min(info.Width / 2, info.Height / 2) -
redThickStroke.StrokeWidth);
if (!outlineThePath)
{
canvas.DrawPath(circlePath, blueFill);
canvas.DrawPath(circlePath, redThickStroke);
}
else
{
using (SKPath outlinePath = new SKPath())
{
redThickStroke.GetFillPath(circlePath, outlinePath);
canvas.DrawPath(outlinePath, blueFill);
canvas.DrawPath(outlinePath, redThinStroke);
}
}
}
}
}
Kruh se vyplní a po tahu podle očekávání:
Když klepnete na obrazovku, outlineThePath nastaví se na truehodnotu a PaintSurface obslužná rutina vytvoří nový SKPath objekt a použije ho jako cílovou cestu při volání GetFillPath objektu redThickStroke malování. Tato cílová cesta je pak vyplněna a tahem redThinStroke, což vede k následujícímu:
Dva červené kruhy jasně ukazují, že původní kruhová cesta byla převedena na dva kruhové obrysy.
Tato metoda může být velmi užitečná při vývoji cest, které se mají použít pro metodu SKPathEffect.Create1DPath . Cesty, které zadáte v těchto metodách, se při replikaci cest vždy vyplní. Pokud nechcete, aby byla vyplněna celá cesta, musíte pečlivě definovat osnovy.
Například v ukázce propojeného řetězce byly odkazy definovány řadou čtyř oblouků, z nichž každá byla založena na dvou paprskech a nastínila oblast cesty, která se má vyplnit. Kód ve LinkedChainPage třídě je možné nahradit trochu jinak.
Nejprve budete chtít předefinovat konstantu linkRadius :
const float linkRadius = 27.5f;
const float linkThickness = 5;
Nyní linkPath je to jen dva oblouky založené na jednom poloměru s požadovanými počátečními úhly a úhly úklidu:
using (SKPath linkPath = new SKPath())
{
SKRect rect = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
linkPath.AddArc(rect, 55, 160);
linkPath.AddArc(rect, 235, 160);
using (SKPaint strokePaint = new SKPaint())
{
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.StrokeWidth = linkThickness;
using (SKPath outlinePath = new SKPath())
{
strokePaint.GetFillPath(linkPath, outlinePath);
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(outlinePath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
}
}
}
Objekt outlinePath je pak příjemcem obrysu linkPath při tahu s vlastnostmi zadanými v strokePaint.
Dalším příkladem použití této techniky je další cesta použitá v metodě.
Kombinování efektů cesty
Dvě konečné metody statického SKPathEffect vytváření jsou SKPathEffect.CreateSum a SKPathEffect.CreateCompose:
public static SKPathEffect CreateSum (SKPathEffect first, SKPathEffect second)
public static SKPathEffect CreateCompose (SKPathEffect outer, SKPathEffect inner)
Obě tyto metody kombinují dva efekty cesty k vytvoření složeného efektu cesty. Metoda CreateSum vytvoří efekt cesty, který je podobný dvěma efektům cesty použitý samostatně, zatímco CreateCompose použije jeden efekt cesty (the inner) a pak na to použije outer .
Už jste viděli, jak GetFillPath metoda SKPaint může převést jednu cestu na jinou cestu na SKPaint základě vlastností (včetně PathEffect), takže by neměla být příliš záhadná, jak SKPaint objekt může tuto operaci provést dvakrát se dvěma účinky cesty zadanými v těchto CreateSum metodách CreateCompose .
Jedním zjevných CreateSum použití je definovat SKPaint objekt, který vyplní cestu jedním efektem cesty, a tahá cestu jiným efektem cesty. To je demonstrováno v ukázce Kočky v rámci rámce , který zobrazuje pole koček v rámci s šupilými okraji:
Třída CatsInFramePage začíná definováním několika polí. První pole z třídy můžete rozpoznat v PathDataCatPagečlánku s daty cesty SVG. Druhá cesta je založena na přímce a oblouku pro vzor skallop rámu:
public class CatsInFramePage : ContentPage
{
// From PathDataCatPage.cs
SKPath catPath = SKPath.ParseSvgPathData(
"M 160 140 L 150 50 220 103" + // Left ear
"M 320 140 L 330 50 260 103" + // Right ear
"M 215 230 L 40 200" + // Left whiskers
"M 215 240 L 40 240" +
"M 215 250 L 40 280" +
"M 265 230 L 440 200" + // Right whiskers
"M 265 240 L 440 240" +
"M 265 250 L 440 280" +
"M 240 100" + // Head
"A 100 100 0 0 1 240 300" +
"A 100 100 0 0 1 240 100 Z" +
"M 180 170" + // Left eye
"A 40 40 0 0 1 220 170" +
"A 40 40 0 0 1 180 170 Z" +
"M 300 170" + // Right eye
"A 40 40 0 0 1 260 170" +
"A 40 40 0 0 1 300 170 Z");
SKPaint catStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 5
};
SKPath scallopPath =
SKPath.ParseSvgPathData("M 0 0 L 50 0 A 60 60 0 0 1 -50 0 Z");
SKPaint framePaint = new SKPaint
{
Color = SKColors.Black
};
...
}
Lze catPath použít v SKPathEffect.Create2DPath metodě, pokud SKPaint je vlastnost objektu Style nastavena na Stroke. Nicméně, pokud catPath je použit přímo v tomto programu, pak celá hlava kočky bude naplněna a vousy nebudou ani viditelné. (Vyzkoušejte to!) Je nutné získat osnovu této cesty a použít tuto osnovu SKPathEffect.Create2DPath v metodě.
Konstruktor tuto úlohu provede. Nejprve použije dvě transformace, aby catPath se bod (0, 0) přesunul na střed a zmenšil jeho velikost. GetFillPath získá všechny obrysy obrysů v outlinedCatPath, a tento objekt se používá ve SKPathEffect.Create2DPath volání. Faktory škálování v SKMatrix hodnotě jsou mírně větší než vodorovná a svislá velikost kočky, aby mezi dlaždicemi poskytovaly malou vyrovnávací paměť, zatímco faktory překladu byly odvozeny poněkud empiricky tak, aby byla plná kočka viditelná v levém horním rohu rámce:
public class CatsInFramePage : ContentPage
{
...
public CatsInFramePage()
{
Title = "Cats in Frame";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Move (0, 0) point to center of cat path
catPath.Transform(SKMatrix.MakeTranslation(-240, -175));
// Now catPath is 400 by 250
// Scale it down to 160 by 100
catPath.Transform(SKMatrix.MakeScale(0.40f, 0.40f));
// Get the outlines of the contours of the cat path
SKPath outlinedCatPath = new SKPath();
catStroke.GetFillPath(catPath, outlinedCatPath);
// Create a 2D path effect from those outlines
SKPathEffect fillEffect = SKPathEffect.Create2DPath(
new SKMatrix { ScaleX = 170, ScaleY = 110,
TransX = 75, TransY = 80,
Persp2 = 1 },
outlinedCatPath);
// Create a 1D path effect from the scallop path
SKPathEffect strokeEffect =
SKPathEffect.Create1DPath(scallopPath, 75, 0, SKPath1DPathEffectStyle.Rotate);
// Set the sum the effects to frame paint
framePaint.PathEffect = SKPathEffect.CreateSum(fillEffect, strokeEffect);
}
...
}
Konstruktor pak volá SKPathEffect.Create1DPath skallopovaný rámec. Všimněte si, že šířka cesty je 100 pixelů, ale přechod je 75 pixelů, aby se replikovaná cesta kolem rámce překrývala. Poslední příkaz konstruktoru volá SKPathEffect.CreateSum zkombinovat dva efekty cesty a nastavit výsledek na SKPaint objekt.
Veškerá tato práce umožňuje, aby obslužná rutina PaintSurface byla poměrně jednoduchá. Stačí definovat obdélník a nakreslit ho pomocí framePaint:
public class CatsInFramePage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKRect rect = new SKRect(50, 50, info.Width - 50, info.Height - 50);
canvas.ClipRect(rect);
canvas.DrawRect(rect, framePaint);
}
}
Algoritmy za efekty cesty vždy způsobují, že se zobrazí celá cesta použitá pro stisknutí nebo vyplnění, což může způsobit, že se některé vizuály zobrazí mimo obdélník. Volání ClipRect před DrawRect voláním umožňuje, aby vizuály byly výrazně čistější. (Zkuste to bez výřezu.)
Často se používá SKPathEffect.CreateCompose k přidání nějakého zpoždění do jiného efektu cesty. Určitě můžete experimentovat sami, ale tady je trochu jiný příklad:
Přerušované šrafované čáry vyplní tři tečky čarami, které jsou přerušované. Většina práce ve DashedHatchLinesPage třídě se provádí přímo v definicích polí. Tato pole definují pomlčkový efekt a šrafový efekt. Jsou definovány jako static , protože se pak odkazují ve SKPathEffect.CreateCompose volání v definici SKPaint :
public class DashedHatchLinesPage : ContentPage
{
static SKPathEffect dashEffect =
SKPathEffect.CreateDash(new float[] { 30, 30 }, 0);
static SKPathEffect hatchEffect = SKPathEffect.Create2DLine(20,
Multiply(SKMatrix.MakeScale(60, 60),
SKMatrix.MakeRotationDegrees(45)));
SKPaint paint = new SKPaint()
{
PathEffect = SKPathEffect.CreateCompose(dashEffect, hatchEffect),
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
};
...
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
{
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
}
}
Obslužná PaintSurface rutina musí obsahovat pouze standardní režijní náklady plus jedno volání DrawOval:
public class DashedHatchLinesPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawOval(info.Width / 2, info.Height / 2,
0.45f * info.Width, 0.45f * info.Height,
paint);
}
...
}
Jak jste už zjistili, čáry šrafování nejsou přesně omezeny na vnitřní část oblasti, a v tomto příkladu vždy začínají vlevo pomlčkou:
Teď, když jste viděli efekty cesty, které se liší od jednoduchých teček a pomlček až po podivné kombinace, použijte svou představivost a podívejte se, co můžete vytvořit.













