Xamarin.Forms 描画で考慮すべき2つのこと
Xamarin.Formsで描画する時、考慮しなければならない事項が2つあります。
1つは「デバイス毎の解像度」、そして、もう1つは「PCLとレンダラーのサイズの違い」です。
1つ目は、モバイル端末を相手にする場合の共通的な話ですが、2つ目は、Xamarin.Formsならではのものです。
1 デバイス毎の解像度
プログラマから見るとデバイス上のディスプレイのピクセル数は、直接関係ありません。例えば、1インチを表現するにしても、解像度によってそのドット数は違うからです。
プログラマは、通常、この解像度を考慮した数値であるDIP(Density-independent Pixels 密度に非依存のピクセル)を使用しています。
これは、640pix × 960pixのスクリーンを持つiPhone4も 320pix × 480pixのスクリーンを持つiPhone3もどちらも横幅は、320として扱われていることからも分かります。
「ピクセル数は無視できる」としても、やはり、様々なサイズのモバイル端末を相手にするとき、デバイスごとサイズ(DIP)の把握が必要になる場合もあります。
次の図は、サイズ300でBoxViewを描いた時の状況です。
public class App { public static Page GetMainPage(){ return new MyPage(); } } class MyPage : ContentPage{ public MyPage(){ Content = new BoxView { HorizontalOptions = LayoutOptions.Center,//画面の中央に配置する VerticalOptions = LayoutOptions.Center,//画面の中央に配置する HeightRequest = 300,//縦のサイズ300 WidthRequest = 300,//横のサイズ300 Color = Color.Aqua//水色で描画 }; } }
Android | iOS | Windows Phone |
![]() |
![]() |
![]() |
デバイスごとに、微妙に違うのが分かります。iOSでは、いっぱいいっぱいになっているのに、WindowsPhoneでは、異常に小さい感じです。
先の話から、実サイズは同じはずなので、これで問題ないのであれば、話はここで終わりですが、「どのデバイスでも横幅いっぱいに表示する」というような要件の場合、これでは、問題があります。
つまり、デバイスのサイズの把握が必要になるという事です。
デバイスの幅は、イベントSizeChangedで取得するのが、最も効率的です。デバイスサイズを取得するコードと、その実行画面は次のとおりです。
public class App { public static Page GetMainPage(){ return new MyPage(); } } class MyPage : ContentPage{ public MyPage(){ var label = new Label { HorizontalOptions = LayoutOptions.Center,//画面の中央に配置する VerticalOptions = LayoutOptions.Center//画面の中央に配置する }; Content = label; SizeChanged += (s, a) => { //サイズ変化のイベントでラベルに表示する label.Text = String.Format("{0}×{1}", Width, Height); }; } }
Android | iOS | Windows Phone |
![]() |
![]() |
![]() |
次のコードは、デバイスサイズを考慮して、画面の横幅の90%のBoxViewを描画したものです。
public class App { public static Page GetMainPage(){ return new MyPage(); } } class MyPage : ContentPage{ public MyPage(){ var boxView = new BoxView { HorizontalOptions = LayoutOptions.Center,//画面の中央に配置する VerticalOptions = LayoutOptions.Center,//画面の中央に配置する Color = Color.Aqua//水色で描画 }; Content = boxView; SizeChanged += (s, a) =>{ //サイズが取得できた段階で、BoxViewのサイズを決定する boxView.HeightRequest = boxView.WidthRequest = Width*0.9; }; } }
Android | iOS | Windows Phone |
![]() |
![]() |
![]() |

「Creating Mobile Apps with Xamarin.Forms, Preview Edition」の「Pixels, points, dps, DIPs, and DIUs 」の所に、Xamarin.Formsでのサイズの扱いについて、フォントも含めて詳しく記載がありました。
2 PCL側とレンダラー側のサイズの違い
Xamarin.Formsの唯一の図形描画コントロールであるBoxViewは、矩形しか描画できません。図のような図形が欲しくなった時、レンダラーで記述するしかないでしょう。
この場合、図中のw,a,bのようにサイズを指定することになると思いますが、PCL側で決定したサイズをそのままレンダラー側で使用しても問題ないのでしょうか?
動作確認のため、最初にサイズ200のBoxViewを2つ並べてみます。2つ目は、BoxViewを継承した独自ビューですが、特に何もしていないため、当たり前ですが2つのBoxViewは、まったく同じサイズで表示されています。
Android | iOS | Windows Phone |
![]() |
![]() |
![]() |
public class App { public static Page GetMainPage(){ return new MyPage(); } } class MyPage : ContentPage{ public MyPage(){ //アブソレートレイアウトでコンテンツを配置する var absoluteLayout = new AbsoluteLayout(); //BoxView var boxView = new BoxView { HeightRequest = 200,//高さを200に設定 WidthRequest = 200,//幅を200に設定 Color = Color.Green//緑色で描画 }; absoluteLayout.Children.Add(boxView,new Point(50,50));//座標(50,50)に配置 //BoxViewを継承したMyBoxView var myBoxView = new MyBoxView { HeightRequest = 200,//高さを200に設定 WidthRequest = 200,//幅を200に設定 Color = Color.Green//緑色で描画 }; absoluteLayout.Children.Add(myBoxView, new Point(50, 300));//座標(50,300)に配置 Content = absoluteLayout; } } //BoxViewを継承したクラス public class MyBoxView : BoxView{ //特に何も定義していない }
続いて、拡張クラスであるMyBoxViewのレンダラーを記述して、各デバイスごとに描画してみました。なお、レンダラー側で描画する際にForms側のオブジェクトを参照しそのサイズを使用しました。
下記のコードは、Androidの場合のレンダラーの例です。
[assembly: ExportRenderer(typeof(MyBoxView), typeof(MyBoxViewRenderer))] namespace App1.Droid { class MyBoxViewRenderer :BoxRenderer{ public override void Draw(Canvas canvas){ var myBoxView = (MyBoxView)Element; //Xamarin.Forms側のオブジェクトの取得 using (var paint = new Paint()){ var rect = new RectF(0, 0, (float)myBoxView.Width, (float)myBoxView.Height); paint.Color = myBoxView.Color.ToAndroid(); //塗りつぶしの色を指定 canvas.DrawRoundRect(rect,0,0, paint); //四角形描画(塗りつぶし) } }
そして実行画面は、次のとおりです。
iOS及びWindowPhoneでは問題ありませんが、Androidでは、サイズが約半分になってしまっているのが分かります。
Android | iOS | Windows Phone |
![]() |
![]() |
![]() |
すなわち、Androidでは、Forms側のサイズが、レンダラー側でそのまま使用できないことになります。
結論として、Androidでは、Forms側のサイズを使用する場合、レンダラーの中で、その比率を計算して考慮する必要があるという事です。
下記のコードは、レンダラー内で、サイズの比率を計算して描画しているものです。
internal class MyBoxViewRenderer : BoxRenderer{ public override void Draw(Canvas canvas){ var myBoxView = (MyBoxView) Element; //Xamarin.Forms側のオブジェクトの取得 //PCL側のサイズと、このコントロール上でのサイズの比率を計算する var expand = Width / myBoxView.Width; //PCL側のサイズを使用する場合は、expandを乗じてから使用する var width = myBoxView.Width * expand; //比率を考慮して幅を設定する var height = myBoxView.Height * expand;//比率を考慮して高さを設定する using (var paint = new Paint()){ var rect = new RectF(0, 0, (float) width, (float) height); paint.Color = myBoxView.Color.ToAndroid(); //塗りつぶしの色を指定 canvas.DrawRoundRect(rect, 0, 0, paint); //四角形描画(塗りつぶし) } } }
ちなみに、比率は、1.9・・・って事で、やはり約2倍にする必要があったみたいです。
Androidでの、この比率の考慮さえすれば、あとは、何も考えずにPCL側のサイズをそのまま利用できます。