第3回:Xamarinにおけるコードの共通化とプラットフォーム固有のコードの記述:特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発(3/3 ページ)
Xamarin.Formsで提供されているコードを共通化する方法と、プラットフォーム固有のコードを書く方法を見ていこう。
Custom Rendererで独自のUIコントロールを作成する
最後に紹介するのはCustom Rendererだ。
Xamarin.Formsで提供されているUIコントロールでは機能が不足する場合、あるいはやりたいことを実現できない場合には、Custom Rendererを使う。既存のUIコントロールの外観を変更したり、機能を追加したりできる。また、独自のUIコントロールを作成することも可能だ*3。
Xamarin.FormsのUIコントロールは、次の図のような構造になっている。UIコントロールの定義は共通だが、その描画を実行する「レンダラー」はプラットフォームごとに分かれている。レンダラーが、各プラットフォーム固有のUIコントロールやAPIを使って、実際の表示を行っているのである。
このレンダラーを独自に作成すれば(=Custom Renderer)、外観を変更したり機能を追加したりできるのである。以降の説明は長くなる(=手間はかかる)が難しいものではないので、気楽に挑戦してみてほしい。
UIコントロール/レンダラー/ネイティブコントロール(Mapコントロールの例)
「Customizing a Map - Xamarin」より。
Xamarin.FormsのUIコントロールは、このような構造になっている。これはMapコントロールの例だが、他のUIコントロールも同様だ。
UIコントロールの定義は、プラットフォームに依存しない(上段のMap)。
UIコントロールの外観や機能の実装は、プラットフォームごとの「レンダラー」が受け持っている(中段のMapRenderer)。
レンダラーは、プラットフォーム固有のコントロールを利用する(下段)。
既存のUIコントロールをカスタマイズする手順は、次のようになる。
- 既存のUIコントロールを継承して、新しくUIコントロールを定義する*4
- プラットフォームごとにレンダラーを作成する。レンダラーとUIコントロールを結び付けるには、レンダラーにExportRenderer属性を付ける
- 作成したUIコントロールをXAMLコード内で利用する
*3 本稿では、既存のUIコントロールをカスタマイズする方法を解説する。独自のUIコントロールを全く新しく作る方法は、「Implementing a View - Xamarin」を参照してほしい。
*4 機能を追加しないなら、継承したクラスを作る必要はなく、「2」のステップ(レンダラーの作成)以降だけでよいはずである。ただし、本稿執筆時点では、そのようにするとWindows 8.1/Windows Phone 8.1ではレンダラーが動作しなかった。
例として、Switchコントロール(Xamarin.Forms名前空間)をカスタマイズしてみよう。
Xamarin.FormsのSwitchコントロールは、次の画像のようにプラットフォームによって表示が異なる。Android/iOSはスイッチだけだが、Windows系では「On」「Off」という文字列も表示されるのだ。これはどちらかに統一したいだろう。ここでは、文字列を消すことにしよう。
また、機能を追加する例として、スイッチの左右を反転させるプロパティを付けてみよう。
1. 既存コントロールを継承する
PCLを使うXamarin.Formsのプロジェクトを作り、PCLプロジェクトにクラスを追加する。ソリューション名は「CustomRenderer」、追加するクラスのファイル名は「MySwitch.cs」とする。
MySwitchクラスは、Switchコントロールを継承するだけでなく、スイッチの方向を反転させるための「RightToLeft」プロパティも追加しよう。プロパティはBindableProperty(前回の2ページ目を参照)として実装する。次のコードのようになる。
namespace CustomRenderer
{
public class MySwitch : Xamarin.Forms.Switch
{
// プロパティはBindablePropertyとして実装する
public static readonly Xamarin.Forms.BindableProperty RightToLeftProperty
= Xamarin.Forms.BindableProperty.Create(
nameof(RightToLeft), typeof(bool), typeof(MySwitch), false);
public bool RightToLeft
{
get { return (bool)GetValue(RightToLeftProperty); }
set { SetValue(RightToLeftProperty, value); }
}
}
}
PCLプロジェクトに、既存のUIコントロールを継承したクラスをこのように作成する。
ここにプロパティやイベントを追加することで、UIコントロールの機能を増やせる。ただし、プロパティやイベントに対するプラットフォーム固有の処理(=ネイティブコントロールに対する処理)は、レンダラーの側に記述する。
2. レンダラーを作成する
ここではUWPプロジェクトだけにレンダラーを作成しよう。作成しなかったAndroid/iOSでは、継承元のSwitchコントロールのレンダラーがMySwitchコントロールのレンダラーとして使用される。
UWPプロジェクトにクラスを追加し、ファイル名は「MySwitchRenderer.cs」とする。
レンダラーの書き方は、これから見ていくコードのようになる。
今回のように既存のUIコントロールを継承している場合は、既存のレンダラー(=SwitchRendererクラス)を継承すればよい。
カスタムレンダラーではOnElementChangedメソッドのオーバーライドが必須だ。そこには、必要に応じてイベントハンドラーやリソースの確保/解放と、プラットフォーム固有のUIコントロールを使った処理を記述する。
レンダラー内では、「this.Element」でXamarinのUIコントロールを参照できる(ここでは「MySwitch」)。また、「this.Control」で、実際に表示に使用しているプラットフォーム固有のコントロールを参照できる(ここではWindows Runtimeの「ToggleSwitch」)。
また、ExportRenderer属性を付けて、どのUIコントロールに対するレンダラーなのかを明示する必要がある。
[assembly: Xamarin.Forms.Platform.UWP.ExportRendererAttribute(
typeof(CustomRenderer.MySwitch),
typeof(CustomRenderer.UWP.MySwitchRenderer))]
namespace CustomRenderer.UWP
{
// MySwitchのレンダラー(UWP用)
public class MySwitchRenderer : Xamarin.Forms.Platform.UWP.SwitchRenderer
{
protected override void OnElementChanged(
Xamarin.Forms.Platform.UWP.ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// 必要なら、イベントハンドラーを解除し、ネイティブリソースを解放する
}
if (e.NewElement != null)
{
// 必要なら、イベントハンドラーをセットし、ネイティブリソースを確保する
}
// MySwitchを取得して処理をする
MySwitch msw = this.Element as MySwitch;
// UWPネイティブのToggleSwitchを取得して処理をする
Windows.UI.Xaml.Controls.ToggleSwitch sw = this.Control;
// ……省略……
}
}
}
これはUWP用であるが、Android/iOSでも同様である(名前空間「Xamarin.Forms.Platform.UWP」やネイティブのUIコントロールはプラットフォームごとに異なる)。
表示されている「On」「Off」の文字列を消し、追加したRightToLeftプロパティに対応するには、OnElementChangedを次のコードのようにする。
protected override void OnElementChanged(
Xamarin.Forms.Platform.UWP.ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
……省略……
// MySwitchを取得する
MySwitch msw = this.Element as MySwitch;
// UWPネイティブのToggleSwitchを取得する
Windows.UI.Xaml.Controls.ToggleSwitch sw = this.Control;
// 表示文字列を削除する
sw.OnContent = null;
sw.OffContent = null;
// 追加したプロパティに対応する
sw.FlowDirection
= msw.RightToLeft ? Windows.UI.Xaml.FlowDirection.RightToLeft
: Windows.UI.Xaml.FlowDirection.LeftToRight;
}
先ほどのレンダラーに、「On」「Off」の文字列を消す処理と、RightToLeftプロパティに対応する処理を追加した(太字の部分)。
MySwitchクラスに記述したプロパティには、このようにしてthis.ElementをMySwitchクラスにキャストすることでアクセスできる。
また、イベントを処理するなら、次のコードのようにしてイベントハンドラーを付けたり外したりする(この例はイベントハンドラーに何も記述していないので、実行しても何も起きない)。
protected override void OnElementChanged(
Xamarin.Forms.Platform.UWP.ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// イベントハンドラーを解除する
e.OldElement.Toggled -= Element_Toggled;
}
if (e.NewElement != null)
{
// イベントハンドラーをセットする
e.NewElement.Toggled += Element_Toggled;
}
……省略……
}
private void Element_Toggled(object sender, Xamarin.Forms.ToggledEventArgs e)
{
// スイッチがON/OFFされたときの処理を書く
}
Toggledイベントに応答する例である。先ほどのレンダラーに太字の部分を追加した(イベントハンドラーの「Element_Toggled」メソッドに何も記述していないので、実行しても何も起きない)。
3. 作成したUIコントロールをXAMLコード内で利用する
PCLプロジェクトの「MainPage.xaml」ファイルを次のようにする。作成したUIコントロール(=MySwitchクラス)を使うには、XAMLコードに<local:MySwitch>要素を記述するだけだ。
<?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:CustomRenderer"
x:Class="CustomRenderer.MainPage">
<!--<Label Text="Welcome to Xamarin Forms!"
VerticalOptions="Center"
HorizontalOptions="Center" />-->
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="HorizontalOptions" Value="End" />
</Style>
<Style TargetType="Switch">
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
<Style TargetType="local:MySwitch">
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<Grid VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="標準のSwitch" />
<Switch Grid.Column="1" />
<Switch Grid.Column="2" IsToggled="True" />
<Label Grid.Row="1" Text="Custom Renderer" />
<local:MySwitch Grid.Row="1" Grid.Column="1" />
<local:MySwitch Grid.Row="1" Grid.Column="2" IsToggled="True" />
<Label Grid.Row="2" Text="Custom Renderer" />
<local:MySwitch Grid.Row="2" Grid.Column="1" RightToLeft="True" />
<local:MySwitch Grid.Row="2" Grid.Column="2" RightToLeft="True"
IsToggled="True" />
</Grid>
</ContentPage>
3行×3列のGridに分割し、各行にOFF状態とON状態のスイッチを配置した。
1行目は標準のSwitchコントロール。
2行目は作成したMySwitchコントロール。
3行目もMySwitchコントロールだが、追加したRightToLeftプロパティを設定した。
MySwitchクラスはPCLプロジェクトに定義したので、全てのプラットフォームで使用できる。
MySwitchクラスのレンダラーはUWPプロジェクトだけに定義した。UWPプロジェクトではそのレンダラーが使われ、それ以外のプラットフォームでは既定の(=Switchコントロールの)レンダラーが使用される。
実行すると次の画像のようになる。
Custom Rendererの実行例
上はVisual Studio Emulator for Androidでの実行結果。中はMac上のSimulatorでの実行結果。下はMobile Emulator(Windows 10)での実行結果。なお、Remoted iOS Simulator for Windowsを使用するにはVisual Studio Enterprise Editionのライセンスが必要となった(編集者がCommunity Editionを使っているため、本稿ではMac上のSimulatorでの画面キャプチャーとなっている)。使用方法についてはプレビュー段階の記事だが「XamarinアプリのMacでのビルドとiOS Simulator for Windows」を参照されたい。
UWPでは作成したレンダラーが使われており、RightToLeftプロパティの設定によって3行目のスイッチが左右反転している。
Android/iOSでは既定のレンダラー(=Switchコントロール用のレンダラー)が使われており、RightToLeftプロパティの設定は無視されている。
まとめ
Xamarin for Visual Studioでコードを共通化する方法は2通りある。PCLと共有プロジェクトだ。Xamarin.FormsでもネイティブUI(Xamarin Native)でも、どちらも利用できる。一長一短あるが、筆者の好みはPCLである。
その共通コードの中でプラットフォーム依存のコードを使う方法は、豊富に用意されている。今回は、その中から4つの方法を試してみた。プラットフォーム間でどれだけコードを共通化できるかは、クロスプラットフォーム開発の最も面白い(そして、最も難しい)ポイントであろう。
Copyright© Digital Advantage Corp. All Rights Reserved.