第3回:Xamarinにおけるコードの共通化とプラットフォーム固有のコードの記述特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発(3/3 ページ)

» 2016年12月02日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
前のページへ 1|2|3       

Custom Rendererで独自のUIコントロールを作成する

 最後に紹介するのはCustom Rendererだ。

 Xamarin.Formsで提供されているUIコントロールでは機能が不足する場合、あるいはやりたいことを実現できない場合には、Custom Rendererを使う。既存のUIコントロールの外観を変更したり、機能を追加したりできる。また、独自のUIコントロールを作成することも可能だ*3

 Xamarin.FormsのUIコントロールは、次の図のような構造になっている。UIコントロールの定義は共通だが、その描画を実行する「レンダラー」はプラットフォームごとに分かれている。レンダラーが、各プラットフォーム固有のUIコントロールやAPIを使って、実際の表示を行っているのである。

 このレンダラーを独自に作成すれば(=Custom Renderer)、外観を変更したり機能を追加したりできるのである。以降の説明は長くなる(=手間はかかる)が難しいものではないので、気楽に挑戦してみてほしい。

UIコントロール/レンダラー/ネイティブコントロール(Mapコントロールの例) UIコントロール/レンダラー/ネイティブコントロール(Mapコントロールの例)
Customizing a Map - Xamarin」より。
Xamarin.FormsのUIコントロールは、このような構造になっている。これはMapコントロールの例だが、他のUIコントロールも同様だ。
UIコントロールの定義は、プラットフォームに依存しない(上段のMap)。
UIコントロールの外観や機能の実装は、プラットフォームごとの「レンダラー」が受け持っている(中段のMapRenderer)。
レンダラーは、プラットフォーム固有のコントロールを利用する(下段)。

 既存のUIコントロールをカスタマイズする手順は、次のようになる。

  1. 既存のUIコントロールを継承して、新しくUIコントロールを定義する*4
  2. プラットフォームごとにレンダラーを作成する。レンダラーとUIコントロールを結び付けるには、レンダラーにExportRenderer属性を付ける
  3. 作成した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」という文字列も表示されるのだ。これはどちらかに統一したいだろう。ここでは、文字列を消すことにしよう。

 また、機能を追加する例として、スイッチの左右を反転させるプロパティを付けてみよう。

Switchコントロール(Android)
Switchコントロール(iOS)
Switchコントロール(Windows 10) Switchコントロール
上はAndroid、中はiOS、下はWindows 10(デスクトップ)での表示である。
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); }
    }
  }
}

Switchコントロールを継承したMySwitchクラス(C#)
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;
      // ……省略……
    }
  }
}

レンダラーの基本的な書き方(C#)
これは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;
}

OnElementChangedを実装する(C#)
先ほどのレンダラーに、「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されたときの処理を書く
}

イベントハンドラーを実装する例(C#)
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>

作成したMySwitchコントロールを利用する(XAML)
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)
Custom Rendererの実行例(Mac上のSimulator)
Custom Rendererの実行例(Mobile Emulator) 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つの方法を試してみた。プラットフォーム間でどれだけコードを共通化できるかは、クロスプラットフォーム開発の最も面白い(そして、最も難しい)ポイントであろう。

「特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発」のインデックス

特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発

前のページへ 1|2|3       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。