2019/5/9 改訂:クラス名をGradationXXXからGradientXXXに変更しました。
はじめに
前回の記事にて、Xamarin.Formsでスマホアプリを開発するための初期設定を行いました。
本記事では、Xamarin標準のコントロールでは実現できない「グラデーションの描画」について、SkiaSharpを用いたコントロールの作成方法を紹介します。
SkiaSharpとは
SkiaSharp は Skia Graphics Library という Google のオープンソースのグラフィックエンジンを利用した2D グラフィックスシステムです。Microsoftの公式サイトで紹介しているライブラリです。基本的に、共通プロジェクトでコードを記載することで、Android、iOS、UWPのすべてで同じ描画ができます。
背景色をグラデーションで描画する方法は他にもありますが、SkiaSharpは描画全般に対応しており、画像変換やパス(曲線や多角形など)の描画にも利用できて活用範囲が広いため、SkiaSharpの利用をお勧めします(厳密には、今回紹介するのはSkiaSharp.Formsですが)。
SkiaSharpの詳細は以下を参照ください。
Xamarin.Forms での SkiaSharp グラフィックス
インストール方法
SkiaSharp.Views.Forms を nuget からインストールしてください。
(本記事執筆時点でのバージョンは1.68.0)
チュートリアル
チュートリアルとして、基本的な使い方は下記の記事を参照ください。
SkiaSharp の単純な円を描画
以下にポイントなるソースコードにコメントを付けて解説します。
public class SimpleCirclePage : ContentPage
{
public SimpleCirclePage()
{
// SkiaSharpの描画用コントロールのSKCanvasViewを作成してハンドラを登録
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
// SKCanvasViewの描画時に呼び出されるハンドラ
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
// canvas上にすでに何かを描画済みであればいったんクリアする(透明色にする)
canvas.Clear();
// 線を描画するためのSKPaintを作成(WPFでのBrushのようなもの)
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = Color.Red.ToSKColor(),
StrokeWidth = 25
};
// キャンバスに円を描画する(引数は中心座標X、中心座標Y、半径、SKPaintの順)
canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, paint);
// SKPaintを背景を塗りつぶす用に変更
paint.Style = SKPaintStyle.Fill;
paint.Color = SKColors.Blue;
// キャンバスを円で塗りつぶす
canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, paint);
}
}
グラデーションの描画方法
グラデーションを描画する方法の詳細は以下を参照ください。
SkiaSharp の線形グラデーション
ポイントは、以下のように、SKPaintのShaderプロパティに、グラデーションの始点、終点、色を設定することです。それ以外は、チュートリアルとだいたい同じです。
paint.Shader = SKShader.CreateLinearGradient(
new SKPoint(rect.Left, rect.Top),
new SKPoint(rect.Right, rect.Bottom),
new SKColor[] { SKColors.Red, SKColors.Blue },
new float[] { 0, 1 },
SKShaderTileMode.Repeat);
グラデーション表示できるコントロールの作成
では、グラデーション表示できるコントロールを作成していきます。
私の経験として、アプリで利用するグラデーションは、あらかじめ定義しておいた数十パターンから選択して使うことが多いと思います。そのため、今回作成するコントロールは、グラデーションのenum値(GradientColor)をプロパティに持ち、そのenum値で定義されたグラデーションを表示する構成とします。
また、今回はシンプルに線形グラデーションのみを紹介します(放射状グラデーションは次回)。
まずは、そのenum値(GradientColor)と、グラデーションの設定(GradientModel)と、それを作成するクラス(GradientModelFactory)を以下のように作成します。
/// <summary>
/// グラデーションの色のタイプ
/// </summary>
public enum GradientColor
{
Transparent,
DarkRed,
LightBlue,
DarkYellow,
Black,
Gray,
}
/// <summary>
/// グラデーション設定
/// </summary>
public class GradientModel
{
/// <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.DarkRed:
return new GradientModel()
{
Colors = new SKColor[] { SKColors.Red, SKColors.DarkRed },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.LightBlue:
return new GradientModel()
{
Colors = new SKColor[] { SKColors.AliceBlue, SKColors.LightBlue },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.DarkYellow:
return new GradientModel()
{
Colors = new SKColor[] { Color.FromHex("#FFFFA500").ToSKColor(), Color.FromHex("#FF90B000").ToSKColor() },
ColorPos = new float[] { 0, 1 }
};
case GradientColor.Black:
// 3色のグラデーションの場合はColorsとColorPosに3つを指定
return new GradientModel()
{
Colors = new SKColor[] { SKColors.White, SKColors.LightGray, SKColors.Black },
ColorPos = new float[] { 0, 0.5f, 1 }
};
case GradientColor.Gray:
return new GradientModel()
{
Colors = new SKColor[] { SKColors.LightGray, SKColors.Gray },
ColorPos = new float[] { 0, 1 }
};
default:
throw new Exception("Invalid GradientColor");
}
}
}
上記のGradientModelを用いて背景を描画するコントロール GradientRect クラスを作成します。xamal部分は以下です。
<?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.GradientRect"
HorizontalOptions="Fill" VerticalOptions="Fill">
<ContentView.Content>
<forms:SKCanvasView x:Name="_SkCanvasView"
PaintSurface="SkCanvasView_OnPaintSurface"
HorizontalOptions="Fill" VerticalOptions="Fill"/>
</ContentView.Content>
</ContentView>
GradientRect クラスのコードビハインドは以下です。
public partial class GradientRect : ContentView
{
/// <summary>
/// BackGradientColor 依存関係プロパティ
/// </summary>
public static readonly BindableProperty BackGradientColorProperty = BindableProperty.Create(
"BackGradientColor", // プロパティ名
typeof(GradientColor), // プロパティの型
typeof(GradientRect), // プロパティを持つ View の型
GradientColor.Transparent, // 初期値
BindingMode.TwoWay, // バインド方向
null, // バリデーションメソッド
BackGradientColorPropertyChanged, // 変更後イベントハンドラ
null, // 変更時イベントハンドラ
null);
/// <summary>
/// 背景のグラデーションのタイプ
/// </summary>
public GradientColor BackGradientColor
{
get => (GradientColor)GetValue(BackGradientColorProperty);
set => SetValue(BackGradientColorProperty, value);
}
/// <summary>
/// Borderの色
/// </summary>
public Color BorderColor { get; set; }
/// <summary>
/// Borderの線の太さ
/// </summary>
public float StrokeWidth { get; set; }
/// <summary>
/// 矩形を描画する場合の角丸にする円の半径
/// </summary>
public int CornerRadius { get; set; }
/// <summary>
/// 初期描画済みか
/// </summary>
public bool Initialized { get; private set; } = false;
// 描画するハンドラ
private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKImageInfo info = e.Info;
SKSurface surface = e.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// 描画範囲をコントロール全体として指定
SKRect rect = new SKRect(0, 0, info.Width, info.Height);
// グラデーション設定を取得
GradientModel gradientModel = GradientModelFactory.Instance.CreateGradientModel(BackGradientColor);
if (gradientModel == null)
{
return;
}
// 塗りつぶしのグラデーション用のSKPaintを作成
SKPaint paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Shader = SKShader.CreateLinearGradient(new SKPoint(rect.Left, rect.Top), new SKPoint(rect.Left, rect.Bottom),
gradientModel.Colors, gradientModel.ColorPos, SKShaderTileMode.Clamp)
};
// 背景を描画
canvas.DrawRoundRect(rect, CornerRadius, CornerRadius, paint);
// 枠線用にSKPaintを変更
paint.Style = SKPaintStyle.Stroke;
paint.Shader = null;
paint.Color = BorderColor.ToSKColor();
paint.StrokeWidth = StrokeWidth;
// 枠線の描画
canvas.DrawRoundRect(rect, CornerRadius, CornerRadius, paint);
// 初期描画済み
Initialized = true;
}
// BackGradientColor変更後ハンドラ
private static void BackGradientColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable == null || newValue == null ||
newValue.GetType() != BackGradientColorProperty.ReturnType)
{
return;
}
GradientRect gradientRect = (GradientRect)bindable;
// 初期描画前であれば何もしない
if (gradientRect.Initialized == false)
{
return;
}
// 再描画する
gradientRect._SkCanvasView.InvalidateSurface();
}
}
上記のコントロールは、利用時に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 />
</Grid.RowDefinitions>
<controls:GradientRect Grid.Row="0" BackGradientColor="DarkRed" />
<controls:GradientRect Grid.Row="1" BackGradientColor="LightBlue" />
<!--CornerRadiusを設定すると角丸になる-->
<controls:GradientRect Grid.Row="2" BackGradientColor="DarkYellow" CornerRadius="30" />
<!--BorderColorとStrokeWidthを設定すると枠線も描画する-->
<controls:GradientRect Grid.Row="3" BackGradientColor="Black" BorderColor="DarkBlue" StrokeWidth="10" />
</Grid>
</ContentPage>
上記を実行すると、以下のようになります(画像はAndroidで実行した場合)。
バインドを用いてグラデーションを変更
上記のGradientRectコントロールは、あるオブジェクトをステータスごとに異なる背景色にしたい場合などに活用できます。BackGradientColorプロパティをバインドすれば、バインド先の値に合わせて背景色を変更できます。それができるように、BackGradientColorは依存関係プロパティとして定義した上で、値が変わった時のハンドラ(BackGradientColorPropertyChangedメソッド)で再描画を実行しています。
実際に、ItemクラスのStatusとバインドさせる例を以下に挙げます。
// ステータスを表すenum
public enum Status
{
ToDo,
Doing,
Done,
}
// Statusをプロパティに持つクラス
public class Item : INotifyPropertyChanged
{
private Status _Status;
public Status Status
{
get => _Status;
set
{
_Status = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// StatusをGradientColorに変換するコンバーター
public class StatusToGradientColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var status = (Status)value;
switch (status)
{
case Status.ToDo:
return GradientColor.DarkRed;
case Status.Doing:
return GradientColor.DarkYellow;
case Status.Done:
return GradientColor.LightBlue;
default:
throw new Exception("Invalid status");
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
上記でバインドさせる準備は完了です。あとは、ItemクラスをGradientRectのBindingContextプロパティに設定して、BackGradientColorに Statusプロパティをバインドさせれば、ItemクラスのStatusに合わせて背景色を変更させることができます。バインドの書き方の例は以下です。
<controls:GradientRect BackGradientColor="{Binding Path=Status, Converter={StaticResource StatusToGradientColorConverter}, Mode=OneWay}" />
本記事で紹介したコードを用いて、実際にItemクラスのStatusプロパティを変更させてグラデーションを切り替えるソースコード一式をGitHubにアップしています(まとめ参照)。
まとめ
ステータスとバインドさせて背景色を変更させられるグラデーションコントロールを作成しました。
本記事で紹介したコードはGitHubの以下にアップしています。よろしければ自由に改変してご利用ください。
使う場合は、GradientColorとGradientFactoryにグラデーションの定義を自由に追加してもらうことを想定しています。
GitHubのソースコードはこちら
次回は、線形グラデーションと放射状グラデーションを使い分けて、矩形と円を描画するコントロールを紹介します。
次回の記事はこちら