ぴーさんログ

だいたいXamarin.Formsのブログ

【Xamarin.Forms 2.3プレビュー】Xamarin.Forms Themesを試そうとしたらまだ使えなかったので代わりにMerged Dictionaryの解説する

この記事はThemesの話のようでありながら、その実Xamarin.Forms版Merged Dictionaryの話です。

Xamarin.Forms Themes はEvolve 2016のキーノートで紹介された新機能の1つ、その目標は「デフォルトで美しいUIを提供すること」です。

Live from Evolve: Faster and Easier Mobile App Development with Xamarin.Forms | Xamarin Blog

すでにドキュメントも完備されてます。

Xamarin.Forms Themes - Xamarin

デフォルトとして提供されるLight/Darkテーマを使うにはNuGetからこれらをダウンロードします。

  • Xamarin.Forms.Theme.Base
  • Xamarin.Forms.Theme.Light / Dark

使い方はこんな感じ

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="EvolveApp.App"
             xmlns:light="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Light">
    <Application.Resources>
        <ResourceDictionary MergedWith="light:LightThemeResources" />
    </Application.Resources>
</Application>

早速試そうとしたところ肝心のNuGetパッケージがまだ配信されていなかったので、代わりのThemesの前提となっている ResourceDictionary.MergedWith について解説します。

Xamarin.Forms版Merged Dictionary

ResourceDictionary.MergedWith はXamarin.Forms 2.3から追加されるプロパティです。他のResourceDictionaryクラスを指定することでリソースを連結します。おおよそWPFなどのMergedDictionariesに相当する機能ですね。

WPFなどWindowsXAMLプラットフォームとの比較

  • Windows
    • ResourceDictionary.MergedDictionaries はコレクション型で複数のResourceDictionaryを結合できる。
    • .xamlファイルを直接ResourceDictionaryとして指定できる。
  • Xamarin.Forms
    • ResourceDictionary.MergedWith に指定できるのは1個だけ。(プロパティの型はType)
    • .xamlファイルは指定できない、あくまでコンパイル可能なResourceDictionary派生型である必要がある。

Windows系は分割・集約、Xamarin.Formsは継承・カスタマイズに向いていると言えるでしょう。

さて、Themesの使い方と照らし合わせるとお気づきでしょうか?

そう、詰まるところXamarin.Forms Themesの正体はよくカスタムされたResourceDictionaryなのであります。 (StyleClass実装も含んでいるはずですが、その辺はThemesが使えるようになった際に改めて見ていきましょう)

Xamarin.Forms 2.3からはResourceDictionaryクラスのseald指定が外れるので自分でもThemeが作れます。

例えばこんな感じ

MyTheme.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="XFApp24.MyTheme">
    <Style TargetType="Label">
        <Setter Property="TextColor" Value="Red"/>
    </Style>
</ResourceDictionary>

MyTheme.xaml.cs

using Xamarin.Forms;

namespace XFApp24
{
    public partial class MyTheme : ResourceDictionary
    {
        public MyTheme ()
        {
            InitializeComponent ();
        }
    }
}

ContentPageなどのテンプレートから改造するとXAMLでカスタムResourceDictionaryが書けます。(要InitializeComponent)

これを使ってみると

f:id:ticktack623:20160430004453j:plain

XAMLプレビューにも反映されました。

早くNuGetにThemes配信されないかなー。

【Xamarin.Forms 2.2】Embedded Platform-Specific Controls in Xamarin.Forms

Xamarin.Forms 2.2から Layout.ChildrenContentView.Content に各プラットフォームのネイティブコントロールを埋め込めるようになり、Evolve 2016のキーノートでも紹介されました。 このフィーチャーを使うとCustomRendererよりもカジュアルにネイティブコントロールを利用できます。

キーノートのブログ

Live from Evolve: Faster and Easier Mobile App Development with Xamarin.Forms | Xamarin Blog

ドキュメント

Native Embedding - Xamarin

条件

ネイティブコントロールの埋め込みを行うにはSharedプロジェクト内のコードで、プラットフォーム毎に#ifディレクティブで記述します。PCLプロジェクトでは使用できません。

サンプルコード

次のようなコードをSharedプロジェクトに追加すると、ネイティブコントロールを埋め込む事ができます。

using System;
using Xamarin.Forms;

#if __IOS__
using Xamarin.Forms.Platform.iOS;
using UIKit;
#elif __ANDROID__
using Xamarin.Forms.Platform.Android;
using Android.Widget;
#endif

namespace XFApp25
{
    public class MyPage : ContentPage
    {
        public MyPage ()
        {
            var text = "Embedded Platform-Specific Control in Xamarin.Forms";

            var stackLayout = new StackLayout {
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.Center,
            };
#if __IOS__
            var uiLabel = new UILabel {
                MinimumFontSize = 14f,
                Lines = 0,
                LineBreakMode = UILineBreakMode.WordWrap,
                Text = text + "(iOS)",
            };
            // StackLayoutにUILabelが追加できる!
            stackLayout.Children.Add (uiLabel);
            // これもOK
            //Content = uiLabel.ToView ();
#elif __ANDROID__
            var textView = new TextView(Forms.Context) {
                Text = text + "(Android)",
                TextSize = 14,
            };
            stackLayout.Children.Add (textView);
            // これもOK
            //Content = textView.ToView ();
#endif
            Content = stackLayout;
        }
    }
}

仕組み

LayoutExtensionsクラスに定義された2つの拡張メソッドが使用します。

  • ToView - ネイティブコントロールをラップしてXamarin.FormsのViewに変換する拡張メソッド
  • Add - IList<View>、つまりLayout.Childrenに対する拡張メソッド(内部でToViewを呼ぶ)

これらの拡張メソッドを使うためには各名前空間の参照が必要です。

  • iOS – Xamarin.Forms.Platform.iOS
  • Android – Xamarin.Forms.Platform.Android
  • Windows Runtime – Xamarin.Forms.Platform.WinRT
  • Universal Windows Platform (UWP) – Xamarin.Forms.Platform.UWP

ToView拡張メソッドがネイティブコントロールを NaitiveViewWrapper に変換し、 NaitiveViewWrapperRenderer が汎用的なViewRendererとして働く、という具合ですね。 込み入ったコントロールのために、サイズ決定時に呼ばれるデリゲートなどを渡すことも出来ます。