Flutter 特定頁面切換螢幕方向/iOS強制橫屏/SystemChrome.setPreferredOrientations不起作用 看這裡!

Wos發表於2019-02-17

轉載請標明出處: juejin.im/post/5c68da…
本文出自:Wos的主頁

我此刻的Flutter版本:

Flutter 1.2.0 • channel dev • github.com/flutter/flu…
Framework • revision 06b979c4d5 (3 weeks ago) • 2019-01-25 14:27:35 -0500
Engine • revision 36acd02c94
Tools • Dart 2.1.1 (build 2.1.1-dev.3.2 f4afaee422)

特定頁面旋轉螢幕很簡單:

SystemChrome.setPreferredOrientations([
  ...
]);
複製程式碼

陣列中是您要支援的螢幕方向.

如果想在特定頁面固定橫屏, 您可以這樣寫:

@override
void initState() {
  super.initState();
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeRight,
    DeviceOrientation.landscapeRight,
  ]);
}
複製程式碼

並且在dispose時更改回豎屏

@override
void dispose() {
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);
  super.dispose();
}
複製程式碼

但是!!! 不要走開 本文重點在下面

在Android裝置上, 呼叫此方法可以強制改變螢幕方向. 但在iOS上卻不是這樣

對於iOS, 這個方法表示設定應用支援的螢幕方向, 只有在物理方向改變時才會改變螢幕方向

現在看起來, 這應該是一個Flutter的一個Bug. 有待官方解決

您可關注 issue #13238 追蹤Flutter官方的最新更新

強制改變佈局方向


2019-03-15 更新:

發現已經有大佬將下面的方法封裝成了package (點此跳轉到orientation).

我沒有嘗試過, 大家可以優先嚐試一下這個庫.


既然 Flutter 提供的方法不能強制改變螢幕方向, 那麼我們可以通過外掛的形式, 橋接到iOS原生程式碼中, 通過原生方式改變螢幕方向.

設定應用支援的佈局方向

通過Xcode開啟Flutter專案中的iOS工程, 根據下圖找到Device Orientation 這一項 勾選需要支援的佈局方向, 通過這一步, 預設你現在的應用已經會根據裝置的方向轉變佈局了

圖1

編寫外掛

展開 Runner/Runner資料夾 右鍵->New File 新增兩個新的OC檔案 FlutterIOSDevicePlugin.mFlutterIOSDevicePlugin.h (叫什麼都沒關係) 建立方式看下圖:

圖2

FlutterIOSDevicePlugin.h的內容:

#import <Flutter/Flutter.h>

@interface FlutterIOSDevicePlugin : NSObject<FlutterPlugin>
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller;
- (instancetype)newInstance:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller;
@end
複製程式碼

FlutterIOSDevicePlugin.m的內容:

#import "FlutterIOSDevicePlugin.h"

@interface FlutterIOSDevicePlugin () {
    NSObject<FlutterPluginRegistrar> *_registrar;
    FlutterViewController *_controller;
}
@end

static NSString* const CHANNEL_NAME = @"flutter_ios_device";
static NSString* const METHOD_CHANGE_ORIENTATION = @"change_screen_orientation";
static NSString* const ORIENTATION_PORTRAIT_UP = @"portraitUp";
static NSString* const ORIENTATION_PORTRAIT_DOWN = @"portraitDown";
static NSString* const ORIENTATION_LANDSCAPE_LEFT = @"landscapeLeft";
static NSString* const ORIENTATION_LANDSCAPE_RIGHT = @"landscapeRight";

@implementation FlutterIOSDevicePlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    FlutterMethodChannel* channel = [FlutterMethodChannel
                                     methodChannelWithName:CHANNEL_NAME
                                     binaryMessenger:[registrar messenger]];
    FlutterIOSDevicePlugin* instance = [[FlutterIOSDevicePlugin alloc] newInstance:registrar flutterViewController:nil];
    [registrar addMethodCallDelegate:instance channel:channel];
}

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller {
    FlutterMethodChannel* channel = [FlutterMethodChannel
                                     methodChannelWithName:CHANNEL_NAME
                                     binaryMessenger:[registrar messenger]];
    FlutterIOSDevicePlugin* instance = [[FlutterIOSDevicePlugin alloc] newInstance:registrar flutterViewController:controller];
    [registrar addMethodCallDelegate:instance channel:channel];
}

- (instancetype)newInstance:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller{
    _registrar = registrar;
    _controller = controller;
    return self;
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([METHOD_CHANGE_ORIENTATION isEqualToString:call.method]) {
        NSArray *arguments = call.arguments;
        NSString *orientation = arguments[0];
        NSInteger iOSOrientation;
        if ([orientation isEqualToString:ORIENTATION_LANDSCAPE_LEFT]){
            iOSOrientation = UIDeviceOrientationLandscapeLeft;
        }else if([orientation isEqualToString:ORIENTATION_LANDSCAPE_RIGHT]){
            iOSOrientation = UIDeviceOrientationLandscapeRight;
        }else if ([orientation isEqualToString:ORIENTATION_PORTRAIT_DOWN]){
            iOSOrientation = UIDeviceOrientationPortraitUpsideDown;
        }else{
            iOSOrientation = UIDeviceOrientationPortrait;
        }
        [[UIDevice currentDevice] setValue:@(iOSOrientation) forKey:@"orientation"];
        result(nil);
    } else {
        result(FlutterMethodNotImplemented);
    }
}
@end
複製程式碼

註冊外掛

開啟AppDelegate.mdidFinishLaunchingWithOptions 方法中註冊外掛

#include "FlutterIOSDevicePlugin.h"

...

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
    ...
    
    // flutter: Device Plugin
    [FlutterIOSDevicePlugin registerWithRegistrar:[self registrarForPlugin:@"FlutterIOSDevicePlugin"] flutterViewController:controller];
}
複製程式碼

使用外掛

import 'package:flutter/services.dart';

MethodChannel _channel = const MethodChannel('flutter_ios_device');

@override
void initState() {
  super.initState();
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
  ]).then((_) {
    if (Platform.isIOS) {
      iOSDevicePlugin.changeScreenOrientation(DeviceOrientation.landscapeLeft);
    }
  });
}

@override
void dispose() {
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]).then((_) {
    if (Platform.isIOS) {
      iOSDevicePlugin.changeScreenOrientation(DeviceOrientation.portraitUp);
    }
  });
  super.dispose();
}

Future<void> changeScreenOrientation(DeviceOrientation orientation) {
  String o;
  switch (orientation) {
    case DeviceOrientation.portraitUp:
      o = 'portraitUp';
      break;
    case DeviceOrientation.portraitDown:
      o = 'portraitDown';
      break;
    case DeviceOrientation.landscapeLeft:
      o = 'landscapeLeft';
      break;
    case DeviceOrientation.landscapeRight:
      o = 'landscapeRight';
      break;
  }
  return _channel.invokeMethod('change_screen_orientation', [o]);
}
複製程式碼

到此, 我們的工作基本完成. 可以強制某些特定頁面改變佈局方向.

還沒有結束

在實踐中, 我發現上面這樣的做法會導致一個問題.

如果只想讓特定的頁面可以改變方向(橫屏), 其它頁面一直保持豎屏該怎麼辦?

"圖一" 中, 我們設定了 iOS 的 Device Orientation 只要裝置方向改變了, 佈局就會改變.

現在, 根據圖一的步驟將 Device Orientation 改為 僅 Portrait

修改 AppDelegate.h, 加入 isLandscape 這個屬性

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,assign)BOOL isLandscape;
@end
複製程式碼

AppDelegate.m 中加入下列方法

// 是否允許橫屏
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window{
    if (self.isLandscape) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }
    return UIInterfaceOrientationMaskPortrait;
}
複製程式碼

修改 FlutterIOSDevicePlugin.m

#import "AppDelegate.h"

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    ...
        if ([orientation isEqualToString:ORIENTATION_LANDSCAPE_LEFT]){
            iOSOrientation = UIDeviceOrientationLandscapeLeft;
            ((AppDelegate *)[UIApplication sharedApplication].delegate).isLandscape = YES;
        }else if([orientation isEqualToString:ORIENTATION_LANDSCAPE_RIGHT]){
            iOSOrientation = UIDeviceOrientationLandscapeRight;
            ((AppDelegate *)[UIApplication sharedApplication].delegate).isLandscape = YES;
        }else if ([orientation isEqualToString:ORIENTATION_PORTRAIT_DOWN]){
            iOSOrientation = UIDeviceOrientationPortraitUpsideDown;
            ((AppDelegate *)[UIApplication sharedApplication].delegate).isLandscape = NO;
        }else{
            iOSOrientation = UIDeviceOrientationPortrait;
            ((AppDelegate *)[UIApplication sharedApplication].delegate).isLandscape = NO;
        }
    ...
}
複製程式碼

完成

參考:

SystemChrome.setPreferredOrientations does not force the device to the given orientations until the device is physically rotated #13238
iOS關於橫屏的有關問題
ios啟動頁強制豎屏(進入App後允許橫屏與豎屏)

我對iOS知之甚少, 以上解決方案全憑網上的資料彙總, 暫時沒有能力提供 flutter plugin 供大家快速接入. 如有高手寫了相關庫請告知我, 我會將它放到這篇文章中. 感謝

相關文章