【译】Flutter控制特定的屏幕方向
Sep 28, 2020 — Flutter
原文《Controlling screen orientation in Flutter apps on a per-screen basis》 (opens in a new window),作者是Roman Petrov (opens in a new window)
作者首先介绍了一个SystemChrome (opens in a new window)全局类,通过调用下面的类方法可以设置整个应用的屏幕方向:
setPreferredOrientations(List<DeviceOrientation> orientations) → Future<void>然后封装了一个方法来简化操作:
enum ScreenOrientation { portraitOnly, landscapeOnly, rotating,}
void _setOrientation(ScreenOrientation orientation) { List<DeviceOrientation> orientations; switch (orientation) { case ScreenOrientation.portraitOnly: orientations = [ DeviceOrientation.portraitUp, ]; break; case ScreenOrientation.landscapeOnly: orientations = [ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]; break; case ScreenOrientation.rotating: orientations = [ DeviceOrientation.portraitUp, DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]; break; } SystemChrome.setPreferredOrientations(orientations);}但重点是要在Navigator 进行push时设置屏幕方向,在pop的时候重置为上一个界面的屏幕方向。可以通过自定义一个NavigatorObserver (opens in a new window),实现didPop和 didPush 两个方法来达到目的。
class NavigatorObserverWithOrientation extends NavigatorObserver { @override void didPop(Route route, Route previousRoute) { if (previousRoute.settings.arguments is ScreenOrientation) { _setOrientation(previousRoute.settings.arguments); } else { // Portrait-only is the default option _setOrientation(ScreenOrientation.portraitOnly); } }
@override void didPush(Route route, Route previousRoute) { if (route.settings.arguments is ScreenOrientation) { _setOrientation(route.settings.arguments); } else { _setOrientation(ScreenOrientation.portraitOnly); } }}注意:作者使用了RouteSettings (opens in a new window) 的
arguments字段来存储屏幕方向,如果arguments为null或者不是ScreenOrientation类型,比如自定义的值,那么只设置为portraitOnly。
最后在生成路由的时候指定屏幕方向:
class AppRoutes { static final home = "/"; static final portrait = "/portrait"; static final landscape = "/landscape"; static final rotating = "/rotating";}
RouteSettings rotationSettings(RouteSettings settings, ScreenOrientation rotation) { return RouteSettings(name: settings.name, arguments: rotation);}
class MyApp extends StatelessWidget { final _observer = NavigatorObserverWithOrientation();
Route<dynamic> _onGenerateRoute(RouteSettings settings) { if (settings.name == AppRoutes.home) { return MaterialPageRoute(builder: (context) => HomeScreen()); } else if (settings.name == AppRoutes.portrait) { return MaterialPageRoute(builder: (context) => PortraitScreen()); } else if (settings.name == AppRoutes.landscape) { return MaterialPageRoute( builder: (context) => LandscapeScreen(), settings: rotationSettings(settings, ScreenOrientation.landscapeOnly), ); } else if (settings.name == AppRoutes.rotating) { return MaterialPageRoute( builder: (context) => RotatingScreen(), settings: rotationSettings(settings, ScreenOrientation.rotating), ); } return null; }
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Orientation Demo', theme: ThemeData(primarySwatch: Colors.blue,), onGenerateRoute: _onGenerateRoute, navigatorObservers: [_observer], ); }}完整的工程在这里 (opens in a new window)。作者最后提了一下,这个方案不是完美的,因为屏幕旋转时,路由转场动画也在同一时间发生,这可能导致一些意料之外的问题。但对作者来说,这已经够了,符合他的 App 需求。