Flutter疑难解决:单独让某个页面的电池栏标签颜色改变

一、需求来源

最近项目适配研深色和浅色的功能,某些情况下整个页面顶部是深色图片背景,需要再浅色模式下,电池栏颜色 icon 颜色为白色(浅色模式下一般是黑色)。

跳转页面逻辑:A(黑)->B(白)->C(黑)。

实现思路:

1、AnnotatedRegion

存在问题:在demo项目里正常;但是进入有三百多个页面的项目,它改变的使整个app的电池栏icon 颜色。

2、No Choice(但最终完美实现需求):

dart 复制代码
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);

二、使用示例

1、main.dart

dart 复制代码
navigatorObservers: [
  RouteManagerObserver(),
],

2、PageOne - B(白)

dart 复制代码
class _PageOneState extends State<PageOne> with CurrentOverlayStyleMixin {
  final scrollController = ScrollController();

  @override
  void initState() {
    super.initState();

    RouteManager().isDebug = false;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      Future.delayed(Duration(milliseconds: 300), () {
        //在网络数据加载出来之后调用此方法
        currentOverlayStyleRoutePush();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      body: buildBody(),
    );
  }

  Widget buildBody() {
    return Scrollbar(
      controller: scrollController,
      child: SingleChildScrollView(
        controller: scrollController,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SizedBox(height: 100),
            IconButton(onPressed: onBack, icon: Icon(Icons.arrow_back_ios_new)),
            OutlinedButton(onPressed: onNext, child: const Text("next")),
          ],
        ),
      ),
    );
  }

  Future<void> onBack() async {
    final result = AppNavigator.back();
  }

  Future<void> onNext() async {
    final result = await AppNavigator.toNamed(AppRouter.pageTwo);
    DLog.d(result);
  }

  @override
  SystemUiOverlayStyle get currentOverlayStyle => SystemUiOverlayStyle.dark;

  @override
  SystemUiOverlayStyle get otherOverlayStyle => SystemUiOverlayStyle.light;

  @override
  bool needOverlayStyleChanged({Route? from, Route? to}) {
    final fromName = from?.settings.name;
    final toName = to?.settings.name;
    DLog.d([fromName, toName].join(" >>> "));
    final result = (toName == AppRouter.pageOne);
    return result;
  }
}

三、源码

1、首先需要 RouteManager 路由管理类

dart 复制代码
/// 路由堆栈管理器
class RouteManager {
  static final RouteManager _instance = RouteManager._();
  RouteManager._();
  factory RouteManager() => _instance;
  static RouteManager get instance => _instance;

  /// 是否打印日志
  bool isDebug = false;

  /// 监听(跳转前)列表
  final List<void Function({Route? from, Route? to})> _beforelisteners = [];

  // 添加监听
  void addRouteBeforeListener(void Function({Route? from, Route? to}) cb) {
    if (_beforelisteners.contains(cb)) {
      return;
    }
    _beforelisteners.add(cb);
  }

  // 移除监听
  void removeRouteBeforeListener(void Function({Route? from, Route? to}) cb) {
    _beforelisteners.remove(cb);
  }

  /// 通知所有监听器
  void notifyRouteBeforeListeners({required Route? from, required Route? to}) {
    for (var ltr in _beforelisteners) {
      ltr(from: from, to: to);
    }
  }

  /// 监听列表
  final List<void Function({Route? from, Route? to})> _listeners = [];

  // 添加监听
  void addListener(void Function({Route? from, Route? to}) cb) {
    if (_listeners.contains(cb)) {
      return;
    }
    _listeners.add(cb);
  }

  // 移除监听
  void removeListener(void Function({Route? from, Route? to}) cb) {
    _listeners.remove(cb);
  }

  /// 通知所有监听器
  void notifyListeners({required Route? from, required Route? to}) {
    for (var ltr in _listeners) {
      ltr(from: from, to: to);
    }
  }

  /// 所有路由堆栈
  final List<Route<Object?>> _routes = [];

  /// 当前路由堆栈
  List<Route<Object?>> get routes => _routes;

  /// 当前 PageRoute 路由堆栈
  List<PageRoute<Object?>> get pageRoutes => _routes.whereType<PageRoute>().toList();

  /// 当前 DialogRoute 路由堆栈
  List<RawDialogRoute<Object?>> get dialogRoutes => _routes.whereType<RawDialogRoute>().toList();

  /// 当前 ModalBottomSheetRoute 路由堆栈
  List<ModalBottomSheetRoute<Object?>> get sheetRoutes => _routes.whereType<ModalBottomSheetRoute>().toList();

  /// 当前路由名堆栈
  List<String?> get routeNames => routes.map((e) => e.settings.name).toList();

  /// 之前路由
  Route<Object?>? get preRoute => _preRoute;

  /// 之前路由
  Route<Object?>? _preRoute;

  /// 之前路由 name
  String? get preRouteName => preRoute?.settings.name;

  /// 当前路由
  Route<Object?>? get currentRoute => routes.isEmpty ? null : routes.last;

  /// 当前路由 name
  String? get currentRouteName => currentRoute?.settings.name;

  /// 最近的 PopupRoute 类型路由
  PopupRoute? get popupRoute {
    for (int i = routes.length - 1; i >= 0; i--) {
      final e = routes[i];
      if (e is PopupRoute) {
        return e;
      }
    }
    return null;
  }

  /// 当前路由类型是 PopupRoute
  bool get isPopupOpen => popupRoute != null;

  /// 路由堆栈包含 DialogRoute 类型
  bool get isDialogOpen => popupRoute is DialogRoute;

  /// 路由堆栈包含 ModalBottomSheetRoute 类型
  bool get isSheetOpen => popupRoute is ModalBottomSheetRoute;

  /// 是否存在路由堆栈中
  bool contain(String routeName) {
    return routeNames.contains(routeName);
  }

  /// 路由对应的参数
  Object? getArguments(String routeName) {
    final index = pageRoutes.indexWhere((e) => e.settings.name == routeName);
    if (index == -1) {
      return null;
    }
    final route = pageRoutes[index];
    return route;
  }

  /// 入栈
  void push(Route<dynamic> route) {
    if (_routes.isEmpty || _routes.isNotEmpty && _routes.last != route) {
      _routes.add(route);
    }
  }

  /// 出栈
  void pop(Route<dynamic> route) {
    _routes.remove(route);
  }

  Map<String, dynamic> toJson() {
    final data = <String, dynamic>{};
    data['isDebug'] = isDebug;
    data['routes'] = routes.map((e) => e.toString()).toList();
    data['pageRoutes'] = pageRoutes.map((e) => e.toString()).toList();
    if (dialogRoutes.isNotEmpty) {
      data['dialogRoutes'] = dialogRoutes.map((e) => e.toString()).toList();
    }
    if (sheetRoutes.isNotEmpty) {
      data['sheetRoutes'] = sheetRoutes.map((e) => e.toString()).toList();
    }
    data['routeNames'] = routeNames;
    data['preRoute'] = preRoute.toString();
    data['preRouteName'] = preRouteName;
    data['currentRouteName'] = currentRouteName;
    data['popupRoute'] = popupRoute.toString();
    data['isPopupOpen'] = isPopupOpen;
    data['isDialogOpen'] = isDialogOpen;
    data['isSheetOpen'] = isSheetOpen;
    return data;
  }

  @override
  String toString() {
    const encoder = JsonEncoder.withIndent('  ');
    final descption = encoder.convert(toJson());
    return "$runtimeType: $descption";
  }

  void logRoutes() {
    if (!isDebug) {
      return;
    }

    developer.log(toString());
  }
}

/// 堆栈管理器路由监听器
class RouteManagerObserver extends RouteObserver<PageRoute<dynamic>> {
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    RouteManager()._preRoute = previousRoute;
    RouteManager().notifyRouteBeforeListeners(from: previousRoute, to: route);

    super.didPush(route, previousRoute);
    RouteManager().push(route);

    RouteManager().notifyListeners(from: previousRoute, to: route);
    RouteManager().logRoutes();
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    RouteManager()._preRoute = route;
    RouteManager().notifyRouteBeforeListeners(from: route, to: previousRoute);

    super.didPop(route, previousRoute);
    RouteManager().pop(route);

    RouteManager().notifyListeners(from: route, to: previousRoute);
    RouteManager().logRoutes();
  }

  @override
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    RouteManager()._preRoute = oldRoute;
    RouteManager().notifyRouteBeforeListeners(from: oldRoute, to: newRoute);

    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (oldRoute != null) RouteManager().pop(oldRoute);
    if (newRoute != null) RouteManager().push(newRoute);

    RouteManager().notifyListeners(from: oldRoute, to: newRoute);
    RouteManager().logRoutes();
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
    RouteManager()._preRoute = route;
    RouteManager().notifyRouteBeforeListeners(from: previousRoute, to: route);

    super.didRemove(route, previousRoute);
    RouteManager().pop(route);

    RouteManager().notifyListeners(from: previousRoute, to: route);
    RouteManager().logRoutes();
  }
}

2、CurrentOverlayStyleMixin

scss 复制代码
/// 路由监听 mixin
mixin CurrentOverlayStyleMixin<T extends StatefulWidget> on State<T> {
  @protected
  SystemUiOverlayStyle get currentOverlayStyle;

  @protected
  SystemUiOverlayStyle get otherOverlayStyle;

  @protected
  bool needOverlayStyleChanged({Route? from, Route? to}) {
    throw UnimplementedError("❌$this Not implemented needOverlayStyleChanged");
  }

  @override
  void dispose() {
    RouteManager().removeListener(_onRouteListener);
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    RouteManager().addListener(_onRouteListener);
  }

  void _onRouteListener({Route? from, Route? to}) {
    final fromName = from?.settings.name;
    final toName = to?.settings.name;
    // DLog.d([fromName, toName].join(" >>> "));
    final needChange = needOverlayStyleChanged(from: from, to: to);
    // DLog.d([fromName, toName, needChange].join(" >>> "));
    if (needChange) {
      _onChange(style: currentOverlayStyle); //需要延迟,等UI走完,防止效果被覆盖
    } else {
      _onChange(style: otherOverlayStyle, duration: Duration.zero);
    }
  }

  Future<void> _onChange({
    Duration duration = const Duration(milliseconds: 300),
    required SystemUiOverlayStyle style,
  }) async {
    if (duration == Duration.zero) {
      SystemChrome.setSystemUIOverlayStyle(style);
    } else {
      Future.delayed(duration, () {
        SystemChrome.setSystemUIOverlayStyle(style);
      });
    }
    DLog.d("$this _onChange ${style.statusBarBrightness?.name}");
  }

  /// 电池栏状态修改(push 到新页面回调)
  void currentOverlayStyleRoutePush() {
    Route? from = RouteManager().pageRoutes[RouteManager().pageRoutes.length - 2];
    Route? to = RouteManager().pageRoutes.last;
    _onRouteListener(from: from, to: to);
  }
}

最后、总结

1、总体思路是在页面(请求网络数据成功)刷新出来之后,再改变当前 B 页面电池栏样式(注意别被样式覆盖)。当前页面 push 或者 pop 之后,恢复电池栏样式。所以需要自己实现路由监听。

2、谁能告诉我单个页面中的 AnnotatedRegion 为什么会影响到整个App的电池栏样式?不胜感激

相关推荐
Yeats_Liao7 小时前
Go Web 编程快速入门 13 - 部署与运维:Docker容器化、Kubernetes编排与CI/CD
运维·前端·后端·golang
Yeats_Liao7 小时前
Go Web 编程快速入门 14 - 性能优化与最佳实践:Go应用性能分析、内存管理、并发编程最佳实践
前端·后端·性能优化·golang
蒜香拿铁8 小时前
Angular【http服务端交互】
前端·http·angular.js
西西学代码8 小时前
Flutter---个人信息(3)---实现修改性别
flutter
游戏开发爱好者88 小时前
Fiddler抓包实战教程 从安装配置到代理设置,详解Fiddler使用方法与调试技巧(HTTPHTTPS全面指南)
前端·测试工具·小程序·https·fiddler·uni-app·webview
universe_018 小时前
前端八股之HTTP
前端·网络协议·http
西西学代码9 小时前
Flutter---ListTile列表项组件
flutter
技术砖家--Felix9 小时前
Spring Boot Web开发篇:构建RESTful API
前端·spring boot·restful
yume_sibai10 小时前
TS 常用内置方法
前端·javascript·typescript