Flutter的状态栏导航栏样式与屏幕方向设置
前言
在开发 App 的过程中,我们控制状态栏、导航栏与屏幕的方向都是很方便的,可以在每一个页面中设置。
而在 Flutter 项目中不一样了,由于 Flutter 项目本质来说类似与单页面+多子页面的设计,所以我们不能用控制(不方便)单页面的方式去设置它的状态栏,导航栏与屏幕方向的控制。
Flutter 也知道这一点,它提供了 SystemUiOverlayStyle
与 SystemUiMode
的方式供我们控制状态栏、导航栏与屏幕旋转支持。
一般我们都是在 main.dart 程序的入口中定义全局的设置。如果我们的产品需求不同的页面有不同的状态栏、导航栏,并且支持的旋转方向也不同?那又改怎么做呢?
一、状态栏导航栏设置
我们知道初始化一个 Flutter 项目运行到 iOS设备和Android设备,它的状态栏显示是不同的。
Android 会有一个半透明样式悬浮在顶部的状态栏
而 iOS 则没有这种半透明状态栏,会展示"沉浸式"的效果,而我们为了保证 Android 与 iOS 的 UI 效果统一,往往会给 Android 平台去掉这种半透明的状态栏。
1.1 状态栏导航栏的控制
通过 SystemUiOverlayStyle
我们可以控制状态栏与导航栏的各种属性:
less
if (GetPlatform.isAndroid) {
//指定全局页面的状态栏,导航栏等设置
SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(
statusBarColor: Colors.transparent, //顶部导航栏的状态栏背景颜色
statusBarBrightness: Brightness.light, //顶部状态栏颜色模式
statusBarIconBrightness: Brightness.dark, //顶部状态栏Icon与文本颜色模式
systemNavigationBarDividerColor: Colors.transparent, //底部导航栏分割线颜色
systemNavigationBarColor: Colors.white, //底部导航栏背景颜色
systemNavigationBarIconBrightness: Brightness.dark, //底部导航栏三大金刚键的颜色模式
);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
这里就不多解释了,注释已经很明白了。在 Android 设备上就能保证与 iOS 一致的效果了。
如果我们手动的修改 statusBarColor: Colors.red 并且设置 SplashPage 的背景颜色为 Colors.red ,那么也能实现"沉浸式"的效果了:
那如果我不想展示状态栏或底部导航栏呢?
我们同样可以通过 SystemUiMode
来控制显示。
ini
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top,SystemUiOverlay.bottom]);
第一个参数是 SystemUiMode
的类型,常用的是 manual手动,immersive自动沉浸,immersiveSticky手动沉浸。
手动:
沉浸式:
手动沉浸,当我们滑动顶部或底部会滑出状态与导航栏:
我个人比较喜欢的是手动模式加控制状态栏颜色去控制显示。也就是上面代码展示的样式:
正常:
1.2 页面覆写控制
之前我们是在 main.dart
程序入口中统一处理全局的状态栏与导航栏。那么如果想在不同的页面覆写自有页面的特殊效果怎么办?
我们可以在单独的页面通过 AnnotatedRegion 来控制,例如
less
AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: Colors.green,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: Colors.white),
child: Scaffold(
...
)
或者:
less
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.light,
systemNavigationBarDividerColor: Colors.transparent,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark),
child: Scaffold(
...
效果:
需要注意的是,当页面没有 AppBar 或 SliverAppBar 的时候才能这么用,如果有这两种 Appbar 的话,它会默认使用全局的样式,也就是 main.dart
中声明的全局样式。
我们只要使用 AppBar,不需要重写任何样式就是默认的效果:
而如果我们想修改状态栏、导航栏的样式,则可以通过它们的 systemOverlayStyle 属性单独设置:
比如我设置如下:
css
systemUiOverlayStyle: SystemUiOverlayStyle.light.copyWith(statusBarColor: Colors.red)
那么此时设置页面的 Appbar 的状态栏与导航栏就是这样了:
此时我们返回到首页可以看到,它又回到了全局的样式,完美符合我们的预期。
二、屏幕方向的设置
Flutter 提供了 DeviceOrientation
来控制应用的,如果我想让一个应用支持横竖屏的切换,那么就要这么设置:
ini
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
2.1 普通的方式设置
还是同样的问题,如果我想不同的页面支持不同的横竖屏呢?比如扫码页面、相机页面等页面不适合横屏,这些页面就应该强制竖屏怎么办?
SystemChrome.setPreferredOrientations 目前还不支持在单独的 Page 中覆写,我们只能在页面中设置,并且在页面退出的时候手动恢复。
scala
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<DeviceOrientation> globalOrientations = [
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
];
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
}
@override
void dispose() {
super.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Home Page'),
),
...
);
}
}
这样就简单的实现啦。
2.2 使用路由栈加白名单的方式自动实现
当然如果你按照我的方式【传送门】管理了自己的路由栈,还能通过配置白名单的方式进行自动化管理。
我们在路由常量中添加只垂直方向的白名单。
arduino
//如果页面只需要垂直方向,放入此白名单即可自动处理
static const onlyVerticalOrientationRouterName = [
PERSONAL_FACE_AUTH, //Camera人脸认证页面,保持垂直布局
AUTH_LOGIN, //登录页面,保持垂直布局
SPLASH, //闪屏页面,保持垂直布局
WORKTRAINING, // 培训列表
WORKTRAININGDETAIL, // 培训详情
];
在我们自己的路由管理栈中,进栈与出栈的时候进行判断:
javascript
class MyRouterHistoryManager {
static final MyRouterHistoryManager _instance = MyRouterHistoryManager._internal();
factory MyRouterHistoryManager() {
return _instance;
}
MyRouterHistoryManager._internal();
final List<String?> _routeNames = [];
// 自己记录路由栈 - 存入
void putRouterByName(String? routeName) {
if (routeName != null) {
_routeNames.add(routeName);
try2SetVerticalOrientation(routeName);
}
}
// 自己记录路由栈 - 移除
void removeRouterByName(String? routeName) {
if (routeName != null && _routeNames.contains(routeName)) {
_routeNames.remove(routeName);
try2RestoreOrientation(routeName);
}
}
//尝试设置只竖屏方向展示页面
void try2SetVerticalOrientation(String routeName) {
if (RouterPath.onlyVerticalOrientationRouterName.contains(routeName)) {
//如果包含白名单路由,需要设置垂直
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
}
}
//尝试设置横竖屏方向展示页面
void try2RestoreOrientation(String routeName) {
if (RouterPath.onlyVerticalOrientationRouterName.contains(routeName)) {
//如果包含白名单路由,需要恢复全局
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
}
//获取到全部的RouterName数组
List<String?> get routeNames => _routeNames;
//查询当前栈中是否存在指定的路由名称
bool isRouteExist(String routeName) {
return _routeNames.contains(routeName);
}
// 只是检查栈顶
bool isTopRouteExist(String routeName) {
if (_routeNames.isNotEmpty) {
return _routeNames.first == routeName;
} else {
return false;
}
}
}
即可自动实现,横屏与横竖屏的切换。
后记
当然如果你的产品与设计不需要处理横竖屏切换,你可以直接全局写死竖屏即可,还避免了踩坑。
虽说 Flutter 的横竖屏布局方式已经方便了,但是还是有很多坑,例如超出布局边界之类的,特别是横竖屏布局在图片的尺寸方面需要大精力适配。
当然了,横竖屏适配之后在平板上、折叠屏上会有更好的提现。如果要做好确实是要花一点心思。
关于状态栏、导航栏控制,关于横竖屏支持的配置,如果有更多的更好的其他方式,也希望大家能评论区交流一起学习进步。
如果我的代码或者注释、讲解等不到位或错漏的地方,希望同学们可以指出。
如果感觉本文对你有一点点的启发,还望你能点赞
支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。