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的电池栏样式?不胜感激

相关推荐
Asurplus几秒前
【VUE】15、安装包管理工具yarn
前端·vue.js·npm·node.js·yarn
liangshanbo12152 分钟前
Mac M3 安装 Antigravity Agent “已损坏“ 问题解决方案
前端·macos·antigravity
sszdlbw2 分钟前
前后端在服务器的部署
运维·服务器·前端·后端
马卫斌 前端工程师5 分钟前
vue 多个请求要同时拉取数据,写一个方法
前端·javascript·vue.js
苏打水com6 分钟前
第十篇:Day28-30 工程化优化与部署——从“能跑”到“好用”(对标职场“项目上线”需求)
前端·css·vue·html·js
写代码的皮筏艇7 分钟前
react hooks中的useState
前端·javascript
fruge8 分钟前
React Fiber 架构详解:为什么它能解决页面卡顿问题?
前端·react.js·架构
时712 分钟前
iframe 事件无法冒泡到父窗口的解决方案
前端·element
用户66006766853912 分钟前
纯 CSS 复刻星战开场:让文字在宇宙中滚动
前端·css