LoginSignup
6
6

More than 5 years have passed since last update.

Xamarin無償化をよそにXamarin.FormsビューとUWPネイティブビューの融合の話

Posted at

Xamarin 無償化!

//build/ 2016にて

Xamarinが無償化しましたね。
待ちに待ってた情報です。
昨年Windows Phone関連でライセンスもらってたんですが…。
これからも使える!というのは大きいです。

ま、今回はそれはそれとして…。

Xamarin.Forms

Xamarin.Formsというのは、iOS / Android / WindowsPhoneで共通に使える画面表示の仕組みを実装したものです。
以前は上記3系統でしたが、今ではWindows 8 Store / UWPも対応しているので、デスクトップからモバイルまでまさに1コードで同一の操作感を実現できる仕組みです。
詳しくは、メインサイト日本語情報等を参考にしてください。
このあたりは先刻ご存知、ということで話を進めます。

さて、今回は基本的にUWPでの話で、サンプルソースはGitHubに置いています。

Xamarin.FormsビューとUWPネイティブビューの融合

Xamarin.FormsでNative Viewを使う

Xamarin.Formsのビューは、UWPの標準ビューを「Xamarin.Formsのビュークラス」と「レンダラークラス」という薄皮二枚かませて他のプラットフォームとの互換性を持たせています。
実際に使用者側が使うのがビュークラス、 ライブラリ作成者側が苦労する 使用者から隠蔽されているのがレンダラークラス、になり、Nativeのビューはレンダラーが作成と保持、ビュークラスに設定したプロパティをNativeのビューに反映させる、という動作をします。

レンダラーはViewRenderer<>を継承したクラスをしています。
これはパラメータに2つのTypeを指定し、前がXamarin.FormsのViewクラス、次がNative Viewクラスです。
この設定によってこのレンダラーがViewとNative Viewを関連づけている、ということを宣言しています。

また、ソースファイルの最初に assembly: ExportRenderer を書かなければいけません。
これは引数にXamarin.FormsのViewとレンダラーのTypeを取ります。
この宣言によって、Viewとレンダラーを関連づけています。

サンプル(Test1.uwp/MainPage.xaml.cs

このサンプルはXamarin.FormsでNative Viewを使うだけのものになっています。

まずは8行目、

[assembly: ExportRenderer(typeof(Test1.uwp.XamarinView.NativeView), typeof(Test1.uwp.Native.NativeViewRenderer))]

です。
assembly: は、ソースの最初に書かなければいけない、ということでここに書いてあります。
この宣言で Test1.uwp.XamarinView.NativeView のレンダラーは Test1.uwp.Native.NativeViewRenderer であることを宣言しています。

次に11行目、

public class NativeViewRenderer : ViewRenderer<Test1.uwp.XamarinView.NativeView, Windows.UI.Xaml.Controls.TextBlock>

これで NativeViewRenderer クラスは Test1.uwp.XamarinView.NativeView を表示する際に Windows.UI.Xaml.Controls.TextBlock を使うことを宣言しています。

そして、Xamarin.FormsのViewの NativeView は72行目で宣言しています。

public class NativeView : View
{
    public static readonly BindableProperty TextProperty = BindableProperty.Create("ItemTextWidth", typeof(string), typeof(string), String.Empty, BindingMode.OneWay, null, null, null, null);

    public string Text
    {
        get { return (string)base.GetValue(NativeView.TextProperty); }
        set { base.SetValue(NativeView.TextProperty, value); }
    }
}

Viewを継承してTextプロパティを宣言しているだけのクラスですね。

このクラスを実際に作っているのは106行目、

Content = new Test1.uwp.XamarinView.NativeView()
{
    Text = "Hello, world !!",
},

NativeViewを作ると、自動的にレンダラーが作られ、レンダラーの OnElementChanged 関数が呼ばれます。
これは15行目にあり、その中の19行目からの

if (Control == null)
{
     nativeView = new Windows.UI.Xaml.Controls.TextBlock()
     {
        FontSize = 60,
     };
     SetNativeControl(nativeView);
}

ここでNative Viewが設定されていなければ作って設定しています。

Xamarin.FormsのViewの子要素としてNative Viewを配置する

これは先の NativeView を、そのまま子要素を持てるXamarin.Formsのビューの子要素として追加するだけです。

サンプル(Test2.uwp/MainPage.xaml.cs

今度はTest1.uwpから NativeViewNativeViewRenderer はそのまま、106行からのViewの定義のみ変更しています。

Content = new StackLayout()
{
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center,
    Padding = new Thickness(50),
    BackgroundColor = Color.Aqua,
    Children = {
        new Test2.uwp.XamarinView.NativeView()
        {
            Text = "Hello, world !!",
            BackgroundColor = Color.White,
        },
    },
}

Test1.uwpのViewの作成時に NativeView の親要素として StackLayout を追加しました。
これでウインドウ中央、50pxのボーダーに囲まれたテキスト表示になります。

Native Viewの子要素としてXamarin.FormsのViewを配置する

さて。
すでにあるNative Viewや、上記で作ったレンダラーが保持しているNative View に直接Xamarin.FormsのViewを配置するにはどうしたらいいでしょうか。

実は、ViewRendererは Windows.UI.Xaml.Controls.Panel を継承しているので、Viewからレンダラーを取得して子要素として付け加えるとコントロールツリーの中に追加されて使えるようにはなります。

StackPanel basePanel = new StackPanel()
{
    Name = "TextBase",
    HorizontalAlignment = HorizontalAlignment.Stretch,
    VerticalAlignment = VerticalAlignment.Stretch,
};
Label textLabel = new Label()
{
    HorizontalOptions = LayoutOptions.FillAndExpand,
    VerticalOptions = LayoutOptions.Start,
    HorizontalTextAlignment = Xamarin.Forms.TextAlignment.Center,
    VerticalTextAlignment = Xamarin.Forms.TextAlignment.Start,
    FontSize = 40,
    Text = "Hello, world",
};
LabelRenderer renderer = (LabelRenderer)textLabel.GetOrCreateRenderer();
basePanel.Children.Add(renderer);

こんな感じですね。

ところで、なんで「使えるように なります」、というような書き方をしているかといいますと。
実はこのままではtextLabelは表示されません。
Windows10 + Visual Studio 2015 Update 2でデバッグ実行してライブビジュアルツリーで見ていただくと、以下のようになります。

ライブビジュアルツリー表示.PNG

TextBase [StackPanel] の子要素の [Panel] つまり、LabelRendererのRenderSizeが0x0になっています。
ViewRendererはArrangeOverrideやMeasureOverrideのメソッドを持っており、中でオーバーライドしているはずなのですが、大きさ計算が想定される値を計算していないようです。
nativeView.LayoutUpdated 内でrendererやtextLabelをArrangeしてやるとちゃんと表示されるので、計算するようにしてしまえば動作はさせられますが、せっかく 手抜きのために Xamarin.Formsを使っているのにコードが増えてしまったら本末転倒です。

本当はこれがやりたかったんですが、どうもそういうわけで一旦あきらめたのでした。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6