はじめに
前回の記事にて、Xamarin.FormsでSkiaSharpを用いて線形グラデーションの矩形を描画するコントロールを作成しました。
本記事では、前回作成したコントロールを修正して、線形と放射状のグラデーションを使い分けて矩形と円を描画するコントロールを作成します。
クラスの構造
グラデーションで、なんらかの形状を描画するabstractのクラスとして、GradientShapeクラスを作成します。
前回作成した矩形を描画する GradientRectクラスは、GradientShapeクラスのサブクラスとなります。さらに、GradientShapeクラスのサブクラスとして、円を描画するGradientCircleクラスを作成します。以下にクラス図を記します。
グラデーションの描画に必要なクラス
今回も各コントロールは、グラデーションのenum値(GradientColor)をプロパティに持ち、そのenum値で定義されたグラデーションを表示する構成とします。
以下に、そのenum値(GradientColor)と、グラデーションの設定(GradientModel)と、それを作成するクラス(GradientModelFactory)のソースコードを記します。
前回との大きな違いは、GradientModelクラスが、線形グラデーションと放射状グラデーションを識別するためのDrawTypeプロパティを持っている点です。
/// <summary>
/// グラデーションの色のタイプ
/// </summary>
public enum GradientColor
{
Transparent,
// 以下、 線形グラデーション
LinerDarkRed,
LinerLightBlue,
LinerDarkYellow,
LinerBlack,
LinerGray,
// 以下、放射状グラデーション
RadialRedBrush,
RadialBlueBrush,
RadialYellowBrush,
}
/// <summary>
/// グラデーション設定
/// </summary>
public class GradientModel
{
/// <summary>
/// グラデーション形状のタイプ
/// </summary>
public ShaderType ShaderType { get; set; }
/// <summary>
/// グラデーションで使用する色
/// </summary>
public SKColor[] Colors { get; set; }
/// <summary>
/// グラデーションのポジション
/// </summary>
public float[] ColorPos { get; set; }
}
/// <summary>
/// グラデーションの設定を作成するクラス
/// </summary>
public class GradientModelFactory
{
/// <summary>
/// コンストラクタ
/// </summary>
private GradientModelFactory() { }
/// <summary>
/// インスタンス
/// </summary>
public static GradientModelFactory Instance => new GradientModelFactory();
/// <summary>
/// グラデーションの設定を作成して返す
/// </summary>
/// <param name="gradientColor">グラデーションの色のタイプ</param>
/// <returns>グラデーションの設定</returns>
public GradientModel CreateGradientModel(GradientColor gradientColor)
{
switch (gradientColor)
{
case GradientColor.Transparent:
// 透明色の場合はnullを返す
return null;
case GradientColor.LinerDarkRed:
return new GradientModel()
{
ShaderType = ShaderType.Liner,
Colors = new SKColor[] { SKColors.Red, SKColors.DarkRed },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.LinerLightBlue:
return new GradientModel()
{
ShaderType = ShaderType.Liner,
Colors = new SKColor[] { SKColors.AliceBlue, SKColors.LightBlue },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.LinerDarkYellow:
return new GradientModel()
{
ShaderType = ShaderType.Liner,
Colors = new SKColor[] { Color.FromHex("#FFFFA500").ToSKColor(), Color.FromHex("#FF90B000").ToSKColor() },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.LinerBlack:
return new GradientModel()
{
// 3色のグラデーションの場合はColorsとColorPosに3つを指定
ShaderType = ShaderType.Liner,
Colors = new SKColor[] { SKColors.White, SKColors.LightGray, SKColors.Black },
ColorPos = new float[] { 0, 0.5f, 1 }
};
case GradientColor.LinerGray:
return new GradientModel()
{
ShaderType = ShaderType.Liner,
Colors = new SKColor[] { SKColors.LightGray, SKColors.Gray },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.RadialRedBrush:
return new GradientModel()
{
// 放射状グラデーション
ShaderType = ShaderType.Radial,
Colors = new SKColor[] { SKColors.White, Color.FromHex("#FFFF2020").ToSKColor() },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.RadialBlueBrush:
return new GradientModel()
{
// 放射状グラデーション
ShaderType = ShaderType.Radial,
Colors = new SKColor[] { SKColors.White, Color.FromHex("#FF2020FF").ToSKColor() },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.RadialYellowBrush:
return new GradientModel()
{
// 放射状グラデーション
ShaderType = ShaderType.Radial,
Colors = new SKColor[] { SKColors.White, SKColors.Yellow },
ColorPos = new float[] { 0, 1 }
};
default:
throw new Exception("Invalid GradientColor");
}
}
グラデーション表示するコントロールの作成
上記のクラスを用いて、GradientShapeおよびそのサブクラスを作成します。
GradientShapeのxaml部分は以下です(前回記事のGradientRectと同じです)。
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="XamarinGradientShape.Controls.GradientShape"
HorizontalOptions="Fill" VerticalOptions="Fill">
<ContentView.Content>
<Grid x:Name="_MainGrid">
<forms:SKCanvasView x:Name="_SkCanvasView" IgnorePixelScaling="True" PaintSurface="SkCanvasView_OnPaintSurface"
HorizontalOptions="Fill" VerticalOptions="Fill"/>
</Grid>
</ContentView.Content>
</ContentView>
GradientShapeクラスのコードビハインドは以下です。
ポイントは、PaintSurfaceメソッド内で、背景を描画するDrawFillメソッドとDrawStrokeメソッド(いずれもabstractメソッド)を呼び出している点です。この2つのメソッドをサブクラスで実装することで、矩形と円の描画を実現します。
(SkCanvasViewなどのSkiaSharpの使い方は前回記事を参照ください)
/// <summary>
/// 背景をグラデーション描画するコントロール
/// </summary>
public abstract partial class GradientShape : ContentView
{
// コンストラクタとプロパティの定義は省略(詳細はGitHubのソースコード参照)
// 描画するハンドラ
private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKImageInfo info = e.Info;
SKSurface surface = e.Surface;
SKCanvas canvas = surface.Canvas;
PaintSurface(canvas, info);
}
/// <summary>
/// 対象キャンバスを描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="info">描画対象領域のサイズなどの情報</param>
protected virtual void PaintSurface(SKCanvas canvas, SKImageInfo info)
{
// 初期描画済みとする
Initialized = true;
// canvas上にすでに何かを描画済みであればいったんクリアする(透明色にする)
canvas.Clear();
// 描画範囲をコントロール全体として指定
SKRect rect = new SKRect(0, 0, info.Width, info.Height);
// グラデーション設定を取得
GradientModel gradientModel = GradientModelFactory.Instance.CreateGradientModel(BackGradientColor);
if (gradientModel == null && BackgroundColor == Color.Transparent)
{
// グラデーション設定もグラデーション無しの背景色も、どちらも未設定であれば透明とする
return;
}
// 塗りつぶしのグラデーション用のSKPaintを作成
using (SKPaint paint = CreateSKPaint())
{
if (gradientModel != null)
{
// 背景グラデーション用のShaderの設定
paint.Shader = CreateShader(gradientModel, rect);
}
else
{
// グラデーション設定がない場合はBackgroundColorから取得
paint.Color = BackgroundColor.ToSKColor();
}
// 背景を描画
DrawFill(canvas, paint, rect);
}
// 枠線の太さが0に近ければ枠線は描画しない
if (StrokeWidth < 0.01) return;
// 枠線用のSKPaintを作成
using (SKPaint paint = CreateSKPaint(BorderColor.ToSKColor(), SKPaintStyle.Stroke, StrokeWidth))
{
// 枠線を描画
DrawStroke(canvas, paint, rect);
}
}
/// <summary>
/// 背景を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected abstract void DrawFill(SKCanvas canvas, SKPaint paint, SKRect rect);
/// <summary>
/// 枠線を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected abstract void DrawStroke(SKCanvas canvas, SKPaint paint, SKRect rect);
/// <summary>
/// 矩形から円の半径を算出する
/// </summary>
/// <param name="rect">矩形</param>
/// <returns>半径</returns>
protected static float GetRadius(SKRect rect)
{
// 円から絶対にはみ出ないように半径は1小さくする
return (float)Math.Min(rect.Width, rect.Height) / 2 - 1;
}
/// <summary>
/// SKPaintを作成する
/// </summary>
/// <param name="style">塗りつぶしか枠線か</param>
/// <param name="strokeWidth">枠線の場合の線の太さ</param>
/// <returns>作成したSKPaint</returns>
protected static SKPaint CreateSKPaint(SKPaintStyle style = SKPaintStyle.Fill, float strokeWidth = 0)
{
return CreateSKPaint(SKColors.Black, style, strokeWidth);
}
/// <summary>
/// SKPaintを作成する
/// </summary>
/// <param name="color">色</param>
/// <param name="style">塗りつぶしか枠線か</param>
/// <param name="strokeWidth">枠線の場合の線の太さ</param>
/// <returns>作成したSKPaint</returns>
protected static SKPaint CreateSKPaint(SKColor color, SKPaintStyle style = SKPaintStyle.Fill, float strokeWidth = 0)
{
return new SKPaint()
{
IsAntialias = true,
Color = color,
Style = style,
StrokeWidth = strokeWidth,
};
}
/// <summary>
/// グラデーション用のShaderを作成する
/// </summary>
/// <param name="gradientModel">グラデーション設定</param>
/// <param name="rect">対象の矩形範囲</param>
/// <returns>作成したShader</returns>
protected static SKShader CreateShader(GradientModel gradientModel, SKRect rect)
{
switch (gradientModel.ShaderType)
{
case ShaderType.Liner:
// 矩形の場合
return SKShader.CreateLinearGradient(
new SKPoint(rect.Left, rect.Top),
new SKPoint(rect.Left, rect.Bottom),
gradientModel.Colors,
gradientModel.ColorPos,
SKShaderTileMode.Clamp);
case ShaderType.Radial:
// 円の場合
// 矩形のサイズから円の半径を算出する
float radius = GetRadius(rect);
// グラデーションの始点位置を中心から左上にずらし、そのずれた分だけ半径を大きくする
return SKShader.CreateRadialGradient(
new SKPoint(rect.MidX - radius * 0.5f, rect.MidY - radius * 0.5f),
radius + radius * 0.5f,
gradientModel.Colors,
gradientModel.ColorPos,
SKShaderTileMode.Clamp);
default:
return null;
}
}
}
矩形を描画するGradientRectクラスと、円を描画するGradientCircleクラスは以下です。これらのサブクラスでの実装内容は、abstractメソッド2つの実装のみです。
/// <summary>
/// グラデーションを実現する矩形
/// </summary>
public class GradientRect : GradientShape
{
/// <summary>
/// 背景を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected override void DrawFill(SKCanvas canvas, SKPaint paint, SKRect rect)
{
canvas.DrawRoundRect(rect, CornerRadius, CornerRadius, paint);
}
/// <summary>
/// 枠線を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected override void DrawStroke(SKCanvas canvas, SKPaint paint, SKRect rect)
{
canvas.DrawRoundRect(rect, CornerRadius, CornerRadius, paint);
}
}
/// <summary>
/// グラデーションを実現する円
/// </summary>
public class GradientCircle : GradientShape
{
/// <summary>
/// 背景を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected override void DrawFill(SKCanvas canvas, SKPaint paint, SKRect rect)
{
float radius = GetRadius(rect);
canvas.DrawCircle((float)rect.MidX, (float)rect.MidY, radius, paint);
}
/// <summary>
/// 枠線を描画する
/// </summary>
/// <param name="canvas">描画対象のキャンバス</param>
/// <param name="paint">描画する色の設定</param>
/// <param name="rect">描画サイズ</param>
protected override void DrawStroke(SKCanvas canvas, SKPaint paint, SKRect rect)
{
float radius = GetRadius(rect);
canvas.DrawCircle(rect.MidX, rect.MidY, radius, paint);
}
}
放射状グラデーションによる円の描画方法
上記で作成したコントロールの使い方を説明します。
以下のように、各コントロールのBackGradientColorを設定すれば、定義したグラデーションで矩形や円を描画します。
<?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:local="clr-namespace:XamarinGradientShape"
xmlns:controls="clr-namespace:XamarinGradientShape.Controls;assembly=XamarinGradientShape"
x:Class="XamarinGradientShape.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<!--円を線形グラデーションで描画-->
<controls:GradientCircle Grid.Row="0" BackGradientColor="LinerDarkRed" />
<!--円を放射状グラデーションで描画-->
<controls:GradientCircle Grid.Row="1" BackGradientColor="RadialBlueBrush" />
<!--矩形にCornerRadiusを設定すると角丸になる-->
<controls:GradientRect Grid.Row="2" BackGradientColor="LinerLightBlue" CornerRadius="30" />
<!--矩形にBorderColorとStrokeWidthを設定すると枠線も描画する-->
<controls:GradientRect Grid.Row="3" BackGradientColor="LinerBlack" BorderColor="DarkBlue" StrokeWidth="10" />
<Button Grid.Row="4" Text="バインドで値が変わる確認用ボタン" Clicked="Button_OnClicked" />
</Grid>
</ContentPage>
上記を実行すると、以下のようになります(画像はAndroidで実行した場合)。
まとめ
線形と放射状のグラデーションを使い分けて矩形と円を描画するコントロールを作成しました。
本記事で紹介したソースコードを、GitHubの以下にアップしました。よろしければ、自由に改変してご利用ください。
使う場合は、GradientColorとGradientFactoryにグラデーションの定義を自由に追加してもらうことを想定しています。
GitHubのソースコードはこちら
次回は、日本語を既定の言語にして多言語対応する方法を紹介します。
次回の記事はこちら