Xamarin.Formsで背景色をグラデーション表示 - NControl編


[前回](http://qiita.com/sudachi808/items/b35bec1fb4ce6f5dc345)はカスタムレンダラーを使用しましたが、やはりXamarin.Formsではできるだけネイティブコードを書きたくないので、カスタムレンダラー無しでやってみたいと思います。

[NControl](https://github.com/chrfalch/NControl) というクロスプラットフォームの2D描画パッケージを使うとグラデーションを描画できるっぽいです（実際の描画は依存パッケージである [NGraphics](https://github.com/praeclarum/NGraphics) が担当）。


## パッケージ追加

ソリューション内の各プロジェクト（PCL/iOS/Android）に、下記のパッケージを追加します。

* NControl
* NControl.Controls

![nuget.png](https://qiita-image-store.s3.amazonaws.com/0/169798/39e5bb8f-1d19-7c51-b6f9-6b6cdb2a72c4.png "nuget.png")

## 初期化

NControl の初期化コードを追加します。

### iOS

```csharp:AppDelegate.cs
using System;
using System.Collections.Generic;
using System.Linq;

using Foundation;
using UIKit;

using NControl.iOS; // <-- 追加

namespace MyApp.iOS
{
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            NControlViewRenderer.Init();    // <-- 追加

            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }
    }
}
```

### Android

```csharp:MainActivity.cs
using System;

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

using NControl.Droid;   // <-- 追加

namespace MyApp.Droid
{
    [Activity(Label = "MyApp.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            NControlViewRenderer.Init();    // <-- 追加

            LoadApplication(new App());
        }
    }
}
```

## 実装

```csharp:GradientView.cs
using System;

using Xamarin.Forms;

using NControl.Abstractions;
using NControl.Controls;
using NGraphics;

namespace MyApp
{
    public class GradientView : NControlView
    {
        public GradientView()
        {
            DrawingFunction = (canvas, rect) =>
            {
                var brush = new LinearGradientBrush(
                    NGraphics.Point.Zero,
                    NGraphics.Point.OneY,
                    this.StartColor.ToNColor(),
                    this.EndColor.ToNColor()
                );
                canvas.FillRectangle(rect, brush);
            };
        }
        
        protected void OnColorChanged()
        {
            this.Invalidate();
        }

        public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
            "StartColor", typeof(Xamarin.Forms.Color), typeof(GradientView), Xamarin.Forms.Color.Default,
            propertyChanged: (bindable, oldValue, newValue) => ((GradientView)bindable).OnColorChanged()
        );

        public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
            "EndColor", typeof(Xamarin.Forms.Color), typeof(GradientView), Xamarin.Forms.Color.Default,
            propertyChanged: (bindable, oldValue, newValue) => ((GradientView)bindable).OnColorChanged()
        );

        public Xamarin.Forms.Color StartColor {
            get { return (Xamarin.Forms.Color)GetValue(StartColorProperty); }
            set { SetValue(StartColorProperty, value); }
        }

        public Xamarin.Forms.Color EndColor {
            get { return (Xamarin.Forms.Color)GetValue(EndColorProperty); }
            set { SetValue(EndColorProperty, value); }
        }

    }
}
```

```xml:XAML
<?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:MyApp"
    x:Class="MyApp.MyAppPage">
    
    <local:GradientView StartColor="Olive" EndColor="Teal" />
    
</ContentPage>
```


## 結果

### iOS
![result_ios.png](https://qiita-image-store.s3.amazonaws.com/0/169798/21b5e7a0-24f7-f2a2-fc23-8fa7ac6ab62a.png "result_ios.png")

### Android
![result_android.png](https://qiita-image-store.s3.amazonaws.com/0/169798/53fbd9a4-ed2e-6593-f21f-d7d0cdbd6dfd.png "result_android.png")

できました。
カスタムレンダラーと比べると簡単な感じがしますね。


## 3色のグラデーション

3色でもグラデーションできます。

```csharp:GradientView.cs
using System;

using Xamarin.Forms;

using NControl.Abstractions;
using NControl.Controls;
using NGraphics;

namespace MyApp
{
    public class GradientView : NControlView
    {
        public GradientView()
        {
            DrawingFunction = (canvas, rect) =>
            {
                LinearGradientBrush brush;
                    
                if (this.MidColor != Xamarin.Forms.Color.Default) {
                    brush = new LinearGradientBrush(
                        NGraphics.Point.Zero,
                        NGraphics.Point.OneY,
                        this.StartColor.ToNColor(),
                        this.MidColor.ToNColor(),
                        this.EndColor.ToNColor()
                    );
                } else {
                    brush = new LinearGradientBrush(
                        NGraphics.Point.Zero,
                        NGraphics.Point.OneY,
                        this.StartColor.ToNColor(),
                        this.EndColor.ToNColor()
                    );
                }
                
                canvas.FillRectangle(rect, brush);
            };
        }
        
        protected void OnColorChanged()
        {
            this.Invalidate();
        }

        public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
            "StartColor", typeof(Xamarin.Forms.Color), typeof(GradientView), Xamarin.Forms.Color.Default,
            propertyChanged: (bindable, oldValue, newValue) => ((GradientView)bindable).OnColorChanged()
        );

        public static readonly BindableProperty MidColorProperty = BindableProperty.Create(
            "MidColor", typeof(Xamarin.Forms.Color), typeof(GradientView), Xamarin.Forms.Color.Default,
            propertyChanged: (bindable, oldValue, newValue) => ((GradientView)bindable).OnColorChanged()
        );

        public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
            "EndColor", typeof(Xamarin.Forms.Color), typeof(GradientView), Xamarin.Forms.Color.Default,
            propertyChanged: (bindable, oldValue, newValue) => ((GradientView)bindable).OnColorChanged()
        );

        public Xamarin.Forms.Color StartColor {
            get { return (Xamarin.Forms.Color)GetValue(StartColorProperty); }
            set { SetValue(StartColorProperty, value); }
        }

        public Xamarin.Forms.Color MidColor {
            get { return (Xamarin.Forms.Color)GetValue(MidColorProperty); }
            set { SetValue(MidColorProperty, value); }
        }

        public Xamarin.Forms.Color EndColor {
            get { return (Xamarin.Forms.Color)GetValue(EndColorProperty); }
            set { SetValue(EndColorProperty, value); }
        }

    }
}
```

```xml:XAML
<?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:MyApp"
    x:Class="MyApp.MyAppPage">
    
    <local:GradientView StartColor="Olive" MidColor="Red" EndColor="Teal" />
    
</ContentPage>
```

### iOS
![result_ios_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/69fcc5e4-2f68-537d-6231-754794f4d6e9.png "result_ios_2.png")

### Android
![result_android_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/0999ee88-e57f-f594-3cb3-8e68d922bd4d.png "result_android_2.png")


若干不自然な感じもしなくはないですが、使いようによっては効果があるのではないでしょうか。


カスタムレンダラーを書かなくても、代わりにやってくれる便利なパッケージを使うと簡単に実装できますね。

とりあえずは以上です。