Xamarin.Formsで枠線つきの円を描く


枠線つきの円を描く必要にせまられたのでやってみました。

## Frame を利用

Xamarin.Formsに円を描画する専用のコントロールはありませんが Frame を使えば描画可能です。

```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">
    
    <Frame
        BackgroundColor="Aqua"
        OutlineColor="Navy"
        WidthRequest="100"
        HeightRequest="100"
        CornerRadius="50"
        Padding="0"
        VerticalOptions="Center"
        HorizontalOptions="Center">
    </Frame>
    
</ContentPage>
```

こんな XAML にすると、

![ios_1.png](https://qiita-image-store.s3.amazonaws.com/0/169798/ce5c7f86-d623-f201-3d40-d810c844872e.png "ios_1.png")

と、枠線のついた円が描けるのでこれでOKと思いきや Android だと、

![a23_1.png](https://qiita-image-store.s3.amazonaws.com/0/169798/37c76471-e662-2921-728a-f5b0163a4846.png "a23_1.png")

枠線がつかねーじゃねーか！

実機でも同じ。
XAML でなく C# のコードでも試してみたものの枠線つかず。
<small>
[プログラミングXamarin](http://ec.nikkeibp.co.jp/item/books/P98860.html)では枠線付きの Frame が Android のキャプチャー画像付きで紹介されているんですが、どういうことなんでしょうかね...。
</small>


## Frame をネストしてみる

仕方ないので Frame を Frame で囲んでみます。

```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">
    
    <Frame
        BackgroundColor="Navy"
        WidthRequest="102"
        HeightRequest="102"
        CornerRadius="51"
        Padding="1"
        VerticalOptions="Center"
        HorizontalOptions="Center">
        <Frame
            BackgroundColor="Aqua"
            WidthRequest="100"
            HeightRequest="100"
            CornerRadius="50"
            Padding="0"/>
    </Frame>
    
</ContentPage>
```

これで、

![ios_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/52156a0a-cdce-6e23-d9e3-307468d5c08a.png "ios_2.png")

いいですね。

![a23_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/76a71025-2caf-1bc3-2232-2c92832a0a24.png "a23_2.png")

いいですね。
じゃ、ついでに KitKat でも、

![a19_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/f96bed25-bdfc-db9c-975a-50bbc75e03fb.png "a19_2.png")

.....。


## NControl を使ってみる

どうにも納得いかない結果になったので、別の手段をとってみます。

しかし、カスタムレンダラーは書きたくない。

ということで [NControl](https://github.com/chrfalch/NControl) というクロスプラットフォームな2D描画パッケージを使ってみます。

### インストール

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

* NControl
* NControl.Controls

NControl.Controls は必須ではないはずですが、Xamarin.Formsの色をNControlの色に変換する拡張メソッドが追加されてなにかと便利なので入れておくと良いと思います。

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

 
### 初期化

iOS/Android に 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());
        }
    }
}
```

### カスタムコントロールを作成

XAMLだけだと無理なんで、NControlView を継承したビューを作成します。

DrawingFunction というプロパティに描画用の関数を設定します。
FillEllipse というのが、1つめの引数で指定した領域を、2番目の色を使って楕円形を塗りつぶすメソッドです。
NControlView は Content プロパティを持っているので、これで NcontrolViewをネストして枠線と内側の円を描画します。

```csharp:CircleView.cs
using System;

using Xamarin.Forms;

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

namespace MyApp
{
    public class CircleView : NControlView
    {
        public CircleView()
        {
            Padding = 1;
            
            DrawingFunction = (canvas, rect) => {
                canvas.FillEllipse(rect, Xamarin.Forms.Color.Navy.ToNColor());
            };
            
            Content = new NControlView {
                DrawingFunction = (canvas, rect) => {
                    canvas.FillEllipse(rect, Xamarin.Forms.Color.Aqua.ToNColor());
                },
            };
        }
    }
}
```

```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:CircleView
        WidthRequest="100"
        HeightRequest="100"
        VerticalOptions="Center"
        HorizontalOptions="Center"
     />
    
</ContentPage>
```


### 結果

![ios_3.png](https://qiita-image-store.s3.amazonaws.com/0/169798/8a1c3187-5a45-e1c5-857d-260d5c376b62.png "ios_3.png")

![a23_3.png](https://qiita-image-store.s3.amazonaws.com/0/169798/3b6cbb21-fd12-28a6-937d-e3ff57c21409.png "a23_3.png")

![a19_3.png](https://qiita-image-store.s3.amazonaws.com/0/169798/9b19b03c-7cac-b2af-7c92-f6fd970915ef.png "a19_3.png")

うまくいったようです。
KitKat でも枠線が表示されていますね。

影がついていませんが、枠線つきの円を描画できました。
カスタムコントロールにバインド可能プロパティを追加すれば、XAMLから色指定とかいろいろできるかと。

NControl を使えば円だけでなく、多角形や曲線をつかったオブジェクトも<b>カスタムレンダラーを使わずに</b>実装できそうです。

とりあえずは以上です。