しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin で iOS と Windows Phone 8 用 BottomBar を作る

Windows ランタイムアプリにおける BottomAppBar くらいなら Xamarin.Forms に用意されているのではと探してみましたが、やはりない!

Android はライセンスを持ってないのでできませんが、Xamarin で iOSWindows Phone 8.0 で BottomBar というカスタム View を作ってみました

ソリューション構成はこんな感じです

f:id:matatabi_ux:20140927212224p:plain

BottomBar.xaml を Xamarin.Forms に作り、iOSWindows Phone 固有の ViewRenderer として BottomBarRenderer を作る構成です

Windows Phone の MainPage.xaml.cs を見ると下記のような実装になっていました

/// <summary>
/// メイン画面
/// </summary>
public partial class MainPage : PhoneApplicationPage
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MainPage()
    {
        InitializeComponent();

        Forms.Init();
        Content = XamarinSample.App.GetMainPage().ConvertPageToUIElement(this);
    }
}

ページそのものではなく、Content に Xamarin.Forms のページを流し込んでいるようですね

ApplicatiionBar は PhoneApplicationPage.ApplicationBar に設定しないといけないので、この実装だと BottomBar を直接 ApplicationBar に委譲させることはできません

なので、Windows Phone の BottombarRenderer は下記のようにしました

[assembly: ExportRenderer(typeof(BottomBar), typeof(BottomBarRender))]

namespace XamarinSample.WinPhone.Views
{
    /// <summary>
    /// BottomBar のレンダリングクラス
    /// </summary>
    public class BottomBarRender : ViewRenderer<BottomBar, Canvas>
    {
        /// <summary>
        /// Element 変更イベントハンドラ
        /// </summary>
        /// <param name="e">イベント引数</param>
        protected override void OnElementChanged(ElementChangedEventArgs<BottomBar> e)
        {
            base.OnElementChanged(e);

            this.Loaded += this.OnLoaded;
        }

        /// <summary>
        /// 読み込み完了イベントハンドラ
        /// </summary>
        /// <param name="sender">イベント発行者</param>
        /// <param name="e">イベント引数</param>
        private void OnLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Loaded -= this.OnLoaded;

            // MainPage を引っ張り出す
            var currentPage = App.RootFrame.Content as PhoneApplicationPage;
            if (currentPage == null)
            {
                return;
            }

            currentPage.ApplicationBar = new ApplicationBar();
        }
    }
}

Frame が描画されたあとになる Page の Loaded イベント発生時に ApplicationBar を Page のプロパティに設定するというわけです

また ApplicationBar は Page を押し上げて表示されるので・・・

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.Views.BottomBar"
             >
  <ContentView.HeightRequest>
    <OnPlatform x:Key="BottomHeight" x:TypeArguments="x:Double"
              iOS="44"
              Android="48"
              WinPhone="0" />
  </ContentView.HeightRequest>
</ContentView>

Xamarin.Forms の BottomBar.xaml の方は Windows Phone だけ高さ 0 にして画面の上部が余計につぶれないようにします

あとは iOS 側の BottomBarRenderer

[assembly: ExportRenderer(typeof(BottomBar), typeof(BottomBarRender))]

namespace XamarinSample.iOS.Views
{
    /// <summary>
    /// BottomBar のレンダリングクラス
    /// </summary>
    public class BottomBarRender : ViewRenderer<BottomBar, UIToolbar>
    {
        /// <summary>
        /// Element 変更イベントハンドラ
        /// </summary>
        /// <param name="e">イベント引数</param>
        protected override void OnElementChanged(ElementChangedEventArgs<BottomBar> e)
        {
            base.OnElementChanged(e);

            var toolBar = new UIToolbar()
            {
                BarStyle = UIBarStyle.Default,
            };

            // UIToolBar をネイティブコントロールとして設定
            this.SetNativeControl(toolBar);

            // 画面描画を要求
            this.SetNeedsDisplay();
        }
    }
}

こちらは UIToolBar に委譲して画面に配置するだけ

こんな感じでそれぞれのプラットフォームで実行すると

f:id:matatabi_ux:20140927214348p:plain f:id:matatabi_ux:20140927214355p:plain

それぞれプラットフォームに適した BottomBar が表示されました!