1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Xamarin開発記03 - 線形と放射状のグラデーションを使い分けて矩形と円を描画

Last updated at Posted at 2019-05-10

はじめに

前回の記事にて、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のソースコードはこちら

次回は、日本語を既定の言語にして多言語対応する方法を紹介します。
次回の記事はこちら

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?