Flutter 特定页面切换屏幕方向/iOS强制横屏/SystemChrome.setPreferredOrientations不起作用 看这里!

9,791 阅读4分钟

转载请标明出处: juejin.cn/post/684490…
本文出自: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 供大家快速接入. 如有高手写了相关库请告知我, 我会将它放到这篇文章中. 感谢