在 Xamarin.Forms 專案內,強制 iOS / Android 的裝置螢幕轉向 直向 Portrait / 橫向 Landscape
當在進行 Xamarin.Forms 專案開發的時候,有些時候需要能夠指定這個應用程式的在執行的時候,是可以使用直式 Portrait 螢幕方式來操作,還是限制只能夠使用橫式 Landscape 螢幕方式來操作,此時,若使用者旋轉其手機,螢幕是不會自動在直式與橫式螢幕上來切換的,完全被鎖定住了;像是這樣的情境,可以在原生專案內進行設定即可,一旦設定完成之後,整個應用程式的所有頁面,都會使用相同的模式來顯示與操作。
延續剛剛的設計,若這個應用程式在某個頁面,此時需要可以讓使用者自行決定要使用橫式或者是直式螢幕方式的操作,也就是要覆蓋原先應用程式的設定值;因為這些功能都需要能夠在原生專案下,使用原生 SDK API 進行呼叫,才能夠進行設定,所以,為了要能夠讓 Xamarin.Forms 的應用程式可以做到這樣的需求,在這個範例中將會使用 Prism 的 事件聚合器 Event Aggregator 這個機制,當在 Xamarin.Forms 的程式碼需要指定特殊需求的螢幕轉向 Orientation,就可以使用事件聚合器發出 Publish 特定事件,而在原生專案內,將會訂閱這個特定事件,所以,當這個事件被觸發之後,將會執行所指定的螢幕轉向的 API 呼叫。
這篇文章的專案原始碼可以從 GitHub 取得
Xamarin.Forms 的專案設計說明
在該 Xamarin.Forms 專案內,建立一個 ScreenOrientationPage.xaml 頁面,該頁面內將會有三個按鈕,這三個按鈕分別會指定這個應用程式在手機上是否可以
- 自由旋轉 : 表示使用者可以旋轉螢幕,使用直式或者橫式螢幕的方式來操作
- 限制直式 : 應用程式將會切換成為直式模式來操作,而且不能夠透過旋轉螢幕的方式,切換成為橫式模式。
- 限制橫式 : 應用程式將會切換成為橫式模式來操作,而且不能夠透過旋轉螢幕的方式,切換成為直式模式。
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Button Text="自由旋轉"
Command="{Binding OrientationCommand}" CommandParameter="自由旋轉"/>
<Button Text="限制直式"
Command="{Binding OrientationCommand}" CommandParameter="限制直式"/>
<Button Text="限制橫式"
Command="{Binding OrientationCommand}" CommandParameter="限制橫式"/>
</StackLayout>
而在 ScreenOrientationPage.xaml 頁面的 ViewModel 內,將會透過建構式注入的方式,注入一個 IEventAggregator 具體實作物件,這個物件將會用來發布一個透定的事件;另外將會建立一個
DelegateCommand<string>
這個型別的物件,用來設計當使用點選上述的任何一個按鈕,將需要執行相對應的程式碼,在此,三個按鈕都會綁定到同一個命令物件,並且使用 CommandParameter 的引數值來區分到底是按下了哪個按鈕。
當知道使用者按下了哪個按鈕,現在就可以透過
IEventAggregator
物件,取得特定的事件 ( CustomScreenOrientationEvent ) ,使用 Publish 方法來送出該事件,這裡使用的敘述為 eventAggregator.GetEvent<CustomScreenOrientationEvent>().Publish(customScreenOrientationPayload);
,其中, Publish 方法所傳入的物件,將會是該事件所要傳遞的事件參數物件。public ScreenOrientationPageViewModel(INavigationService navigationService,
IEventAggregator eventAggregator)
{
this.navigationService = navigationService;
this.eventAggregator = eventAggregator;
OrientationCommand = new DelegateCommand<string>(x =>
{
CustomScreenOrientationPayload customScreenOrientationPayload = new CustomScreenOrientationPayload();
if (x == "自由旋轉")
{
customScreenOrientationPayload.CustomScreenOrientation = CustomScreenOrientation.Unspecified;
}
else if (x == "限制直式")
{
customScreenOrientationPayload.CustomScreenOrientation = CustomScreenOrientation.UserPortrait;
}
else
{
// 限制橫式
customScreenOrientationPayload.CustomScreenOrientation = CustomScreenOrientation.UserLandscape;
}
eventAggregator.GetEvent<CustomScreenOrientationEvent>().Publish(customScreenOrientationPayload);
});
}
對於 CustomScreenOrientationEvent 這個事件將會定義在底下的類別中,他需要繼承 PubSubEvent 這個類別,對於這個事件要被觸發的時候,可以透過 CustomScreenOrientationPayload 類別來指定要如何觸發這個事件,在訂閱該事件端,可以透過這個物件決定要如何執行相關程式碼;訂閱端的程式碼將會分別撰寫在 Android / iOS 這兩個原生專案內。
public enum CustomScreenOrientation
{
UserPortrait,
UserLandscape,
Unspecified
}
public class CustomScreenOrientationEvent : PubSubEvent<CustomScreenOrientationPayload>
{
}
public class CustomScreenOrientationPayload
{
public CustomScreenOrientation CustomScreenOrientation { get; set; }
}
如何指定該應用程式預設的螢幕轉向設定
現在,將要來針對 Android 專案進行設定,讓這個 Xamarin.Forms App 只能夠使用直式螢幕顯示的方式來操作。 請打開 Android 專案內的 MainActivity.cs 檔案,找到 class MainActivity 類別的定義程式碼的上方,將會看到 Activity 屬性的宣告,請在最後面加上
ScreenOrientation = ScreenOrientation.Portrait
的設定,這樣表示這個應用程式在執行的時候,不管手機是使用何種方向來操作,都是使用直式螢幕的方式來顯示,也就是會如圖同底下的螢幕截圖效果。[Activity(Label = "ForceOrientation", Icon = "@mipmap/ic_launcher",
Theme = "@style/MainTheme", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App(new AndroidInitializer()));
}
}
對於 iOS 專案,請使用這個設定方式,讓這個 Xamarin.Forms App 在 iOS 系統下執行的時候,只能夠使用直式螢幕顯示的方式來操作。 請打開 iOS 專案內的 Info.plist 檔案,這裡將會是使用 XML 檢視方式來編輯;請找到 UISupportedInterfaceOrientations,請將該文字後面的
<array>...</array>
內容,修改成為只有 <string>UIInterfaceOrientationPortrait</string>
這個宣告項目,這樣表示這個應用程式在執行的時候,不管手機是使用何種方向來操作,都是使用直式螢幕的方式來顯示,也就是會如圖同底下的螢幕截圖效果。
當然,也可以使用滑鼠雙擊 Info.plist 這個檔案節點,此時會出現視覺化的設定畫面,如下圖所示,在這裡可以在 [裝置方向] 的地方,只要勾選 [直向] 這個選項即可,其他的不用勾選,也是可以做到如上面設定的相同效果。
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
由程式來控制螢幕要顯示的方向,到底是直向還是橫向
首先來看看 Android 的專案,同樣的打開 MainActivity.cs 這個檔案,將會新設計一個方法 SubscribePrismEvent(),在這個方法內,將會透過
App.Current.Container
取得 Prism 的 DI Container 容器物件,接著透過該 containerProvider 物件,解析出 IEventAggregator 的具體實作物件出來,如此,便可以透過 eventAggregator.GetEvent<CustomScreenOrientationEvent>().Subscribe
方法來訂閱 CustomScreenOrientationEvent 這個事件,也就是說,當這個事件有被 Publish 的時候,將會觸發這個 Subscribe 的委派方法。
在這個訂閱事件委派方法內,依據該事件傳送進來的 CustomScreenOrientationPayload 物件值,設定 RequestedOrientation 這個屬性值,這樣,螢幕就會依據所指定的條件,自動進行轉向了。最後,請在 OnCreate() 方法內,加入呼叫 SubscribePrismEvent(); 這個方法即可。
例如,在此點選 限制橫式 按鈕,就會將正在以直向顯示的螢幕內容,自動轉換成為橫向顯示了,如下圖所示。
[Activity(Label = "ForceOrientation", Icon = "@mipmap/ic_launcher",
Theme = "@style/MainTheme", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App(new AndroidInitializer()));
SubscribePrismEvent();
}
public void SubscribePrismEvent()
{
IContainerProvider containerProvider = App.Current.Container;
IEventAggregator eventAggregator = containerProvider.Resolve<IEventAggregator>();
eventAggregator.GetEvent<CustomScreenOrientationEvent>().Subscribe(x =>
{
if (x.CustomScreenOrientation == CustomScreenOrientation.Unspecified)
{
RequestedOrientation = ScreenOrientation.Unspecified;
}
else if (x.CustomScreenOrientation == CustomScreenOrientation.UserPortrait)
{
RequestedOrientation = ScreenOrientation.Portrait;
}
else
{
RequestedOrientation = ScreenOrientation.Landscape;
}
});
}
}
接下來看看如何在 iOS 平台下做到由程式來控制螢幕轉向,請打開
AppDelegate.cs
這個檔案,將會新設計一個方法 SubscribePrismEvent(),在這個方法內,將會透過 App.Current.Container
取得 Prism 的 DI Container 容器物件,接著透過該 containerProvider 物件,解析出 IEventAggregator 的具體實作物件出來,如此,便可以透過 eventAggregator.GetEvent<CustomScreenOrientationEvent>().Subscribe
方法來訂閱 CustomScreenOrientationEvent 這個事件,也就是說,當這個事件有被 Publish 的時候,將會觸發這個 Subscribe 的委派方法。
在這個訂閱事件委派方法內,依據該事件傳送進來的 CustomScreenOrientationPayload 物件值,該類別內新增的一個屬性,那就是 ScreenOrientation ,使其成為該事件所期望的螢幕轉向方向,而在 SubscribePrismEvent() 方法內,最後有呼叫
UIViewController.AttemptRotationToDeviceOrientation();
敘述,將會嘗試要將螢幕進行轉向的相關程式碼執行;另外,需要覆寫 public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, [Transient] UIWindow forWindow)
這個方法,在此方法內,依據 ScreenOrientation 屬性值,回傳需要的螢幕轉向設定值。。最後,請在 FinishedLaunching() 方法內,加入呼叫 SubscribePrismEvent(); 這個方法即可。
例如,在此點選 限制橫式 按鈕,就會將正在以直向顯示的螢幕內容,自動轉換成為橫向顯示了,如下圖所示。
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
CustomScreenOrientation ScreenOrientation = CustomScreenOrientation.UserPortrait;
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App(new iOSInitializer()));
SubscribePrismEvent();
return base.FinishedLaunching(app, options);
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, [Transient] UIWindow forWindow)
{
if (ScreenOrientation == CustomScreenOrientation.Unspecified)
{
return UIInterfaceOrientationMask.All;
}
else if (ScreenOrientation == CustomScreenOrientation.UserLandscape)
{
return UIInterfaceOrientationMask.Landscape;
}
else
{
return UIInterfaceOrientationMask.Portrait;
}
}
public void SubscribePrismEvent()
{
IContainerProvider containerProvider = App.Current.Container;
IEventAggregator eventAggregator = containerProvider.Resolve<IEventAggregator>();
eventAggregator.GetEvent<CustomScreenOrientationEvent>().Subscribe(x =>
{
ScreenOrientation = x.CustomScreenOrientation;
NSNumber n ;
NSString key = new NSString("orientation");
if (x.CustomScreenOrientation == CustomScreenOrientation.Unspecified)
{
n = new NSNumber((int)UIInterfaceOrientation.Unknown);
}
else if (x.CustomScreenOrientation == CustomScreenOrientation.UserPortrait)
{
n = new NSNumber((int)UIInterfaceOrientation.Portrait);
}
else
{
n = new NSNumber((int)UIInterfaceOrientation.LandscapeLeft);
}
UIDevice.CurrentDevice.SetValueForKey(
new NSNumber((int)n),
new NSString("orientation"));
UIViewController.AttemptRotationToDeviceOrientation();
});
}
}