如何给每一个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,这一期就此完结。

相关推荐
fundroid18 分钟前
2025 跨平台技术如何选:KMP 与 Flutter 的核心差异
flutter·kotlin·kmp
耳東陈3 小时前
Flutter开箱即用一站式解决方案-新增企业级日志
flutter
顾林海3 小时前
Flutter 图片组件全面解析:从基础加载到高级应用
android·前端·flutter
眼镜会飞3 小时前
Flutter window和Mac中webview2使用Cef替代
windows·flutter·mac
淡写成灰3 小时前
Flutter自定义带有Badger组件组
flutter
好的佩奇4 小时前
Dart 之任务
android·flutter·dart
豪冷啊17 小时前
Flutter Invalid constant value.
flutter
顾林海21 小时前
Flutter容器组件深度解析
android·前端·flutter
xq952721 小时前
mac os flutter 配置环境变量
flutter
sg_knight1 天前
Flutter性能优化终极指南:从JIT到AOT的深度调优
前端·flutter·性能优化·web·dart