如何给每一个Flutter子页面单独设置【样式】与【屏幕方向】

Flutter的状态栏导航栏样式与屏幕方向设置

前言

在开发 App 的过程中,我们控制状态栏、导航栏与屏幕的方向都是很方便的,可以在每一个页面中设置。

而在 Flutter 项目中不一样了,由于 Flutter 项目本质来说类似与单页面+多子页面的设计,所以我们不能用控制(不方便)单页面的方式去设置它的状态栏,导航栏与屏幕方向的控制。

Flutter 也知道这一点,它提供了 SystemUiOverlayStyleSystemUiMode 的方式供我们控制状态栏、导航栏与屏幕旋转支持。

一般我们都是在 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,这一期就此完结。

相关推荐
Flutter社区4 小时前
使用 Flutter 3.19 更高效地开发
flutter·dart
Forever不止如此7 小时前
【CustomPainter】绘制圆环
flutter·custompainter·圆环
wills7778 小时前
Flutter Error: Type ‘UnmodifiableUint8ListView‘ not found
flutter
AiFlutter1 天前
Flutter之Package教程
flutter
Mingyueyixi1 天前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
crasowas2 天前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
老田低代码3 天前
Dart自从引入null check后写Flutter App总有一种难受的感觉
前端·flutter
AiFlutter3 天前
Flutter Web首次加载时添加动画
前端·flutter
ZemanZhang4 天前
Flutter启动无法运行热重载
flutter