Flutter疑难解决:单独让某个页面的电池栏标签颜色改变
2025年10月31日 21:27
一、需求来源
最近项目适配研深色和浅色的功能,某些情况下整个页面顶部是深色图片背景,需要再浅色模式下,电池栏颜色 icon 颜色为白色(浅色模式下一般是黑色)。
跳转页面逻辑:A(黑)->B(白)->C(黑)。
实现思路:
1、AnnotatedRegion
存在问题:在demo项目里正常;但是进入有三百多个页面的项目,它改变的使整个app的电池栏icon 颜色。
2、No Choice(但最终完美实现需求):
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
二、使用示例
1、main.dart
navigatorObservers: [
RouteManagerObserver(),
],
2、PageOne - B(白)
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 路由管理类
/// 路由堆栈管理器
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
/// 路由监听 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的电池栏样式?不胜感激