実機を回転したときに縦置きと横置きの対応する方法は、基本はフローレイアウトを使うほうがいいのですが、コントロールがたくさん並んでいる場合にはなかなか大変ですね。…という理由を付けて、まとまったものが見つからなかったので、3つのプラットフォームでXamarinでどう対応するのかを残しておきます。
それぞれ方法があると思いますが、Potable Class Library で DataModel を共有化することと、3つのプラットフォームで似た方法で作れる、ことを目的とします。
■Windowsストアプリ の場合
- 横置き(Landscape)と縦置き(Portrait)の2つのViewStateを作る。
- PageクラスのLayoutUpdatedイベントで DisplayInformation.GetForCurrentView() でデバイスの向きを判断して、VisualStateManager.GoToState メソッドを呼び出す。
Blend を使って、VisualStateGroup を作って、その中に2つの ViewState を作る。”Landscape” と “Portrait” を作っておく。
コントロールを Portrait を設定してデザインを作る。
画面自体は1画面なので、XAMLに対して、this.DataContext = _model; のようにバインドができる。
回転時はイベントを取得して、ViewState を切り替える。
private void pageRoot_LayoutUpdated(object sender, object e)
{
DisplayInformation displayInfo = DisplayInformation.GetForCurrentView();
switch (displayInfo.CurrentOrientation)
{
// 横置き
case DisplayOrientations.Landscape:
case DisplayOrientations.LandscapeFlipped:
VisualStateManager.GoToState(this, "Landscape", true);
break;
// 縦置き
case DisplayOrientations.Portrait:
case DisplayOrientations.PortraitFlipped:
VisualStateManager.GoToState(this, "Portrait", true);
break;
}
}
横置きと縦置きで、コントロールの位置はこんな風に自由に配置できる。方法としてはスナップも同じ。というかスナップと同じ方法。
■Android の場合
- Alternative Layouts で、landscape 用のレイアウトを作る。
- layout-land/Main.axml を横置き用に編集する。
- ウィジット(コントロール)の ID は元の Portrait と同じにしておく。
回転したときには内部で自動的に判断して、layout-land/Main.axml が呼び出される。*.axml ファイルが2つになるので、IDをそろえておく。揃えておくと、FindViewById メソッドで統一して扱える。
private void UpdateData()
{
FindViewById(Resource.Id.textViewID).Text = _model.ID;
FindViewById(Resource.Id.textViewUserName).Text = _model.UserName;
FindViewById(Resource.Id.textViewScore).Text = _model.Score.ToString();
FindViewById(Resource.Id.textViewRank).Text = _model.Rank.ToString();
}
Android エミュレータは Ctrl+F11 で回転できる。
■iOS の場合
- Storyboard で、横置き用の ViewController を作成する。
- Storyboard Segue に名前を付けおく。
- 元の ViewController の View のタグを付けておく(初期化判断用)
- 同じ ViewController クラスに設定しておく。
- UIDeviceOrientationDidChangeNotification イベントで、PerformSegue メソッドを使って View を切り替える。
最初の画面が縦置き(Portrait)で、横置き(Landscape)に画面遷移する、という想定で作る。
切り替えを「Model」にしておく。
最初の View に Tag を付けておく。これは最初の画面のみ UIDeviceOrientationDidChangeNotification を設定するため。
AwakeFromNib をオーバーライドして UIDeviceOrientationDidChangeNotification イベントを登録。回転イベントが発生したら、UIDevice.CurrentDevice.Orientation で View を切り替える。
bool isShowingLandscaeView ;
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// main view only
if (this.View.Tag == 100) {
// main view only,
// set to main view's tag 100.
this.isShowingLandscaeView = false;
UIDevice.CurrentDevice.BeginGeneratingDeviceOrientationNotifications ();
NSNotificationCenter.DefaultCenter.AddObserver (
"UIDeviceOrientationDidChangeNotification",
orientationChanged);
}
}
void orientationChanged( NSNotification obj )
{
var deviceOrientation = UIDevice.CurrentDevice.Orientation;
if (deviceOrientation == UIDeviceOrientation.LandscapeLeft ||
deviceOrientation == UIDeviceOrientation.LandscapeRight) {
if (isShowingLandscaeView == false) {
this.PerformSegue ("LandView", this);
isShowingLandscaeView = true;
}
} else if (deviceOrientation == UIDeviceOrientation.Portrait ||
deviceOrientation == UIDeviceOrientation.PortraitUpsideDown) {
if (isShowingLandscaeView == true) {
this.DismissViewController (false, null);
isShowingLandscaeView = false;
}
}
}
元画面(portrait)から lanscape に遷移するときは、this.PerformSegue (“LandView”, this); を使い、landscape から元画面(portrait)に戻るときは this.DismissViewController (false, null); を使う。これは、storybord の画面遷移と同じ。遷移のアニメーションと回転時のアニメーションが競合するよな気もするんだが…このほうは Apple のマニュアルにもあるので、大丈夫だと思う。
2つの画面に分かれるが、同じ ViewController を示すようにして、IBOutlet させたプロパティにDataModel から設定する。
■ DataModel を PCL で作る
データ自体は、DataModel 内にひとつにしたいので、複数の View に対応する必要がある。基本は画面遷移をしたときと同じなのだが、画面のコントロール/ウィジットが多い場合には意外と大変かも。ただし、縦横で別々に作ったほうが使いやすいデザインにはなる。
| Winストア | Android | iOS | |
| デザイン | 1つのXAML | 2つの AXMLファイル |
2つのViewController |
| 回転 切り替え |
コード | 自動 | コード |
| データ 設定 |
データバインド | FindViewById で取得。IDを揃える。 | IBOutletで取得 |
上記のサンプルは git で公開しています。https://github.com/moonmile/SampleRotate
機会を作って MvvmCross でも。
