跳到主要内容

【译】Flutter控制特定的屏幕方向

· 阅读需 3 分钟

原文《Controlling screen orientation in Flutter apps on a per-screen basis》,作者是Roman Petrov

设置屏幕方向

作者首先介绍了一个SystemChrome全局类,通过调用下面的类方法可以设置整个应用的屏幕方向:

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,实现didPopdidPush 两个方法来达到目的。

class NavigatorObserverWithOrientation extends NavigatorObserver {

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);
}
}


void didPush(Route route, Route previousRoute) {
if (route.settings.arguments is ScreenOrientation) {
_setOrientation(route.settings.arguments);
} else {
_setOrientation(ScreenOrientation.portraitOnly);
}
}
}

注意:作者使用了RouteSettingsarguments 字段来存储屏幕方向,如果argumentsnull或者不是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;
}


Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orientation Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
onGenerateRoute: _onGenerateRoute,
navigatorObservers: [_observer],
);
}
}

总结

完整的工程在这里。作者最后提了一下,这个方案不是完美的,因为屏幕旋转时,路由转场动画也在同一时间发生,这可能导致一些意料之外的问题。但对作者来说,这已经够了,符合他的 App 需求。