SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Xamarin.Forms 描画で考慮すべき2つのこと

【 Xamarin 記事一覧 】

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
f:id:furuya02:20141117005643p:plain:w150 f:id:furuya02:20141117005642p:plain:w150 f:id:furuya02:20141117005645p:plain:w150

デバイスごとに、微妙に違うのが分かります。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
f:id:furuya02:20141117005851p:plain:w150 f:id:furuya02:20141117005852p:plain:w150 f:id:furuya02:20141117005854p:plain:w150

次のコードは、デバイスサイズを考慮して、画面の横幅の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
f:id:furuya02:20141117010105p:plain:w150 f:id:furuya02:20141117010106p:plain:w150 f:id:furuya02:20141117010108p:plain:w150


f:id:furuya02:20141117012049p:plain:w150
「Creating Mobile Apps with Xamarin.Forms, Preview Edition」の「Pixels, points, dps, DIPs, and DIUs 」の所に、Xamarin.Formsでのサイズの扱いについて、フォントも含めて詳しく記載がありました。

2 PCL側とレンダラー側のサイズの違い

Xamarin.Formsの唯一の図形描画コントロールであるBoxViewは、矩形しか描画できません。図のような図形が欲しくなった時、レンダラーで記述するしかないでしょう。
f:id:furuya02:20141117023650p:plain
この場合、図中のw,a,bのようにサイズを指定することになると思いますが、PCL側で決定したサイズをそのままレンダラー側で使用しても問題ないのでしょうか?

動作確認のため、最初にサイズ200のBoxViewを2つ並べてみます。2つ目は、BoxViewを継承した独自ビューですが、特に何もしていないため、当たり前ですが2つのBoxViewは、まったく同じサイズで表示されています。


Android iOS Windows Phone
f:id:furuya02:20141117020241p:plain:w150 f:id:furuya02:20141117010237p:plain:w150 f:id:furuya02:20141117010238p:plain:w150

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
f:id:furuya02:20141117010235p:plain:w150 f:id:furuya02:20141117010237p:plain:w150 f:id:furuya02:20141117010238p:plain:w150

すなわち、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倍にする必要があったみたいです。
f:id:furuya02:20141117010321p:plain

Androidでの、この比率の考慮さえすれば、あとは、何も考えずにPCL側のサイズをそのまま利用できます。


【 Xamarin 記事一覧 】