Flutter 控制屏幕旋转

4,940 阅读4分钟

最近需要做个平板的项目,然后需要直接横屏,有2种实现方式。

  • 1, 随着屏幕旋转,布局自动调整。做横竖屏适配
  • 2,强制屏幕横屏,不随着屏幕去调整

第一种方式这里就不做说明了。代码做适配就可以。 下面说一下第二种实现方式

Flutter 自带方式

flutter 为我们提供了方法来控制系统的横竖屏显示

 SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown
    ]).then((_) {
    });

包含的方向类型。 !!!!但是但是但是这个方法只适用于android在iOS上没有效果,上网查资料有大神封装的 flutter插件orientation, flutter插件auto_orientation在iOS上都起不到效果,所以打算自己写一个原生文件与flutter进行通讯,实现屏幕旋转。也是笔者在查询资料之后做的一个整合和解释说明

iOS端实现屏幕旋转

创建iOS原生文件

创建iOS原生文件来实现交互,首先要创建个原生文件。命名为FlutterIOSDevicePlugin。 创建文件 创建一个命名为FlutterIOSDevicePlugin.h 和命名为FlutterIOSDevicePlugin.m文件,说明一下: FlutterIOSDevicePlugin.h文件选择图片里面的header File模块创建即可。

创建之后,一起看一下FlutterIOSDevicePlugin.h里面的代码

#ifndef FlutterIOSDevicePlugin_h
#define 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
#endif /* FlutterIOSDevicePlugin_h */

这个不需要过多说明了看一下FlutterIOSDevicePlugin.m的代码

#import <Foundation/Foundation.h>
#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];
        NSNumber *index = [NSNumber numberWithInt: [call.arguments[0] intValue]];
        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"];
//        [[NSNotificationCenter defaultCenter] postNotificationName:@"FlutterIOSDevicePlugin" object:nil userInfo:@{@"orientationMask": index}];
//        [UIViewController attemptRotationToDeviceOrientation];

        result(nil);
    } else {
        result(FlutterMethodNotImplemented);
    }
}
@end

以上是全部的代码,其中尝试了用通知的方式去做屏幕旋转,后来发现其实没有那么麻烦,直接采用的是

[[UIDevice currentDevice] setValue:@(iOSOrientation) forKey:@"orientation"];

方式做的。其中遇到的问题:

问题1:iphone手机上可以正常旋转,ipad上不行

这个问题主要原因是info。plist文件里面的iphone和ipad不一致,且勾选Requires full screen 即可 回到正轨哈

注册原生插件文件

iOS 工程种有个GeneratedPluginRegistrant.m文件,直接注册就可以了。 如果你项目引入了其他第三方插件,也是统一在这个地方注册的

#import "GeneratedPluginRegistrant.h"

#if __has_include(<auto_orientation/AutoOrientationPlugin.h>)
#import <auto_orientation/AutoOrientationPlugin.h>
#else
@import auto_orientation;
#endif

#if __has_include(<orientation/OrientationPlugin.h>)
#import <orientation/OrientationPlugin.h>
#else
@import orientation;
#endif
#import "FlutterIOSDevicePlugin.h"

@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [AutoOrientationPlugin registerWithRegistrar:[registry registrarForPlugin:@"AutoOrientationPlugin"]];
  [OrientationPlugin registerWithRegistrar:[registry registrarForPlugin:@"OrientationPlugin"]];
//    [registry registrarForPlugin:@"FlutterIOSDevicePlugin"];
   

    [FlutterIOSDevicePlugin registerWithRegistrar: [registry registrarForPlugin:@"FlutterIOSDevicePlugin"]];
}

@end

代码中的这个片段是自己写的插件注册的方法,其他的AutoOrientationPlugin,OrientationPlugin是引用第三方插件自动生成的代码。

[FlutterIOSDevicePlugin registerWithRegistrar: [registry registrarForPlugin:@"FlutterIOSDevicePlugin"]];

AppDelegate文件中的设置

import UIKit
import Flutter



@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  var orientationMask: UIInterfaceOrientationMask = .all;
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    NotificationCenter.default.addObserver(self, selector: #selector(changeLandscape(center:)), name:NSNotification.Name(rawValue: "FlutterIOSDevicePlugin"), object: nil)
    GeneratedPluginRegistrant.register(with: self);
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    override func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return orientationMask;
    }
    
    @objc func changeLandscape(center: Notification){
        let index: NSNumber = (center.userInfo?["orientationMask"] ?? 5) as! NSNumber
        var mask : UIInterfaceOrientationMask = .all
        switch index {
        case 0:
            mask = .portrait
            break
        case 1:
            mask = .landscapeLeft
            break
        case 2:
            mask = .landscapeRight
            break
        case 3:
            mask = .portraitUpsideDown
            break
        case 4:
            mask = .landscape
            break
        case 5:
            mask = .all
            break
        case 6:
            mask = .allButUpsideDown
            break
        
        default:
            mask = .all
            break
        }
        orientationMask = mask;
        _ = application(UIApplication.shared, supportedInterfaceOrientationsFor: UIApplication.shared.keyWindow)
    }
}

其中changeLandscape方法是控制监听的事件的方法,目前没什么用。 主要代码是这一段,这个是在发出屏幕旋转的时候回调的方法,这里面设置的是全部的方向都可以。

    override func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return orientationMask;
    }

问题2:程序不走该方法

具体原因是因为info.plist文件问题。参考上面设置就没问题

flutter 原生代码使用

这个地方是针对于iOS平台去做的区别。

MethodChannel _channel = const MethodChannel('flutter_ios_device');

@override
  void initState() {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown
    ]).then((_) {
      if (Platform.isIOS) {
        this.changeScreenOrientation(DeviceOrientation.landscapeLeft);
      }
    });
    super.initState();
  }
  
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]);
  }

代码不用过多说明了, 有不了解的可以留言

问题3: 启动程序自动旋转

SystemChrome.setPreferredOrientations需要设置全部内容。不然会默认旋转。有点小坑,

以上基本上可以实现屏幕旋转的问题,如果有小伙伴还是不行,可以试一下用iOS原生通知的方式去完成。这个我做的是平板的项目,暂时没有问题,欢迎大家来讨论