Flutter状态栏修改
在flutter应用中导航状态栏的字体颜色和背景是由原生实现的,flutter提供了channel的桥接方法和AnnotatedRegion组件来实现, 推荐使用AnnotatedRegion,这也更加符合flutter的代码风格
使用方式
- 通过系统桥接方法实现
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
- 通过Widget包装来实现
AnnotatedRegion<SystemUiOverlayStyle>(
child: childWidget,
value: _systemOverlayStyle(darkMode, backgroundColor),
),
SystemUiOverlayStyle _systemOverlayStyle(bool isDarkMode, [Color? backgroundColor]) {
final SystemUiOverlayStyle style = isDarkMode ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark;
return style.copyWith(statusBarColor: backgroundColor);
}
注意事项
- 当同一个页面使用多个AnnotatedRegion时,从WidgetTree最末端开始查找,找到第一个AnnotatedRegion就使用这个, 而AppBar中会自己设置AnnotatedRegion,所以需要注意他们的层级关系是否冲突
- 一定不要忘记添加范型约束,否则不会生效(这里不会报错,某些场景下用起来会感觉没问题,一旦出现两个页面上下堆叠且它们的背景颜色和状态栏颜色色调不一致时就会出现明显的bug)
AnnotatedRegion的具体应用步骤
当接收到vsync信号开始绘制时会执行compositeFrame方法根据状态栏的坐标去找找对应的layer上是否存在自定义的AnnotatedRegion并取出它们的的SystemUiOverlayStyle数据传递给native去更新状态栏.
主要实现都在这个方法里
void _updateSystemChrome() {
// Take overlay style from the place where a system status bar and system
// navigation bar are placed to update system style overlay.
// The center of the system navigation bar and the center of the status bar
// are used to get SystemUiOverlayStyle's to update system overlay appearance.
//
// Horizontal center of the screen
// V
// ++++++++++++++++++++++++++
// | |
// | System status bar | <- Vertical center of the status bar
// | |
// ++++++++++++++++++++++++++
// | |
// | Content |
// ~ ~
// | |
// ++++++++++++++++++++++++++
// | |
// | System navigation bar | <- Vertical center of the navigation bar
// | |
// ++++++++++++++++++++++++++ <- bounds.bottom
final Rect bounds = paintBounds;
// Center of the status bar
final Offset top = Offset(
// Horizontal center of the screen
bounds.center.dx,
// The vertical center of the system status bar. The system status bar
// height is kept as top window padding.
_window.padding.top / 2.0,
);
// Center of the navigation bar
final Offset bottom = Offset(
// Horizontal center of the screen
bounds.center.dx,
// Vertical center of the system navigation bar. The system navigation bar
// height is kept as bottom window padding. The "1" needs to be subtracted
// from the bottom because available pixels are in (0..bottom) range.
// I.e. for a device with 1920 height, bound.bottom is 1920, but the most
// bottom drawn pixel is at 1919 position.
bounds.bottom - 1.0 - _window.padding.bottom / 2.0,
);
final SystemUiOverlayStyle? upperOverlayStyle = layer!.find<SystemUiOverlayStyle>(top);
/// By default this method simply calls [findAnnotations] with `onlyFirst:
/// true` and returns the annotation of the first result. Prefer overriding
/// [findAnnotations] instead of this method, because during an annotation
/// search, only [findAnnotations] is recursively called, while custom
/// behavior in this method is ignored.
S? find<S extends Object>(Offset localPosition) {
final AnnotationResult<S> result = AnnotationResult<S>();
findAnnotations<S>(result, localPosition, onlyFirst: true); //从树的末端取,找到最近的一个就返回,所以在树的层级上前面的节点中定义的Annotation默认会被覆盖掉.
return result.entries.isEmpty ? null : result.entries.first.annotation;
}
- 具体查找是在AnnotatedRegionLayer进行的
class AnnotatedRegionLayer<T extends Object> extends ContainerLayer {
/// Searches the subtree for annotations of type `S` at the location
/// `localPosition`, then adds the annotation [value] if applicable.
///
/// This method always searches its children, and if any child returns `true`,
/// the remaining children are skipped. Regardless of what the children
/// return, this method then adds this layer's annotation if all of the
/// following restrictions are met:
///
/// {@macro flutter.rendering.AnnotatedRegionLayer.restrictions}
///
/// This search process respects `onlyFirst`, meaning that when `onlyFirst` is
/// true, the search will stop when it finds the first annotation from the
/// children, and the layer's own annotation is checked only when none is
/// given by the children.
///
/// The return value is true if any child returns `true`, or if [opaque] is
/// true and the layer's annotation is added.
///
/// For explanation of layer annotations, parameters and return value, refer
/// to [Layer.findAnnotations].
@override
bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
bool isAbsorbed = super.findAnnotations(result, localPosition, onlyFirst: onlyFirst);
if (result.entries.isNotEmpty && onlyFirst) {
return isAbsorbed;
}
if (size != null && !(offset & size!).contains(localPosition)) {
return isAbsorbed;
}
if (T == S) {
isAbsorbed = isAbsorbed || opaque;
final Object untypedValue = value;
final S typedValue = untypedValue as S;
result.add(AnnotationEntry<S>(
annotation: typedValue,
localPosition: localPosition - offset,
));
}
return isAbsorbed;
}
@override
bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, {required bool onlyFirst}) {
for (Layer? child = lastChild; child != null; child = child.previousSibling) {
final bool isAbsorbed = child.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
if (isAbsorbed) {
return true;
}
if (onlyFirst && result.entries.isNotEmpty) {
return isAbsorbed;
}
}
return false;
}
设置native状态栏
- iOS
- (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
NSString* brightness = message[@"statusBarBrightness"];
if (brightness == (id)[NSNull null]) {
return;
}
UIStatusBarStyle statusBarStyle;
if ([brightness isEqualToString:@"Brightness.dark"]) {
statusBarStyle = UIStatusBarStyleLightContent;
} else if ([brightness isEqualToString:@"Brightness.light"]) {
if (@available(iOS 13, *)) {
statusBarStyle = UIStatusBarStyleDarkContent;
} else {
statusBarStyle = UIStatusBarStyleDefault;
}
} else {
return;
}
NSNumber* infoValue = [[NSBundle mainBundle]
objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
Boolean delegateToViewController = (infoValue == nil || [infoValue boolValue]);
if (delegateToViewController) {
// This notification is respected by the iOS embedder
[[NSNotificationCenter defaultCenter]
postNotificationName:@(kOverlayStyleUpdateNotificationName)
object:nil
userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
} else {
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
}
}
- Android
private void setSystemChromeSystemUIOverlayStyle(
PlatformChannel.SystemChromeStyle systemChromeStyle) {
Window window = activity.getWindow();
View view = window.getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat =
new WindowInsetsControllerCompat(window, view);
if (Build.VERSION.SDK_INT < 30) {
// Flag set to specify that this window is responsible for drawing the background for the
// system bars. Must be set for all operations on API < 30 excluding enforcing system
// bar contrasts. Deprecated in API 30.
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// Flag set to dismiss any requests for translucent system bars to be provided in lieu of what
// is specified by systemChromeStyle. Must be set for all operations on API < 30 operations
// excluding enforcing system bar contrasts. Deprecated in API 30.
window.clearFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
// SYSTEM STATUS BAR -------------------------------------------------------------------
// You can't change the color of the system status bar until SDK 21, and you can't change the
// color of the status icons until SDK 23. We only allow both starting at 23 to ensure buttons
// and icons can be visible when changing the background color.
// If transparent, SDK 29 and higher may apply a translucent scrim behind the bar to ensure
// proper contrast. This can be overridden with
// SystemChromeStyle.systemStatusBarContrastEnforced.
if (Build.VERSION.SDK_INT >= 23) {
if (systemChromeStyle.statusBarIconBrightness != null) {
switch (systemChromeStyle.statusBarIconBrightness) {
case DARK:
// Dark status bar icon brightness.
// Light status bar appearance.
windowInsetsControllerCompat.setAppearanceLightStatusBars(true);
break;
case LIGHT:
// Light status bar icon brightness.
// Dark status bar appearance.
windowInsetsControllerCompat.setAppearanceLightStatusBars(false);
break;
}
}
if (systemChromeStyle.statusBarColor != null) {
window.setStatusBarColor(systemChromeStyle.statusBarColor);
}
}
// You can't override the enforced contrast for a transparent status bar until SDK 29.
// This overrides the translucent scrim that may be placed behind the bar on SDK 29+ to ensure
// contrast is appropriate when using full screen layout modes like Edge to Edge.
if (systemChromeStyle.systemStatusBarContrastEnforced != null && Build.VERSION.SDK_INT >= 29) {
window.setStatusBarContrastEnforced(systemChromeStyle.systemStatusBarContrastEnforced);
}
// SYSTEM NAVIGATION BAR --------------------------------------------------------------
// You can't change the color of the system navigation bar until SDK 21, and you can't change
// the color of the navigation buttons until SDK 26. We only allow both starting at 26 to
// ensure buttons can be visible when changing the background color.
// If transparent, SDK 29 and higher may apply a translucent scrim behind 2/3 button navigation
// bars to ensure proper contrast. This can be overridden with
// SystemChromeStyle.systemNavigationBarContrastEnforced.
if (Build.VERSION.SDK_INT >= 26) {
if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
switch (systemChromeStyle.systemNavigationBarIconBrightness) {
case DARK:
// Dark navigation bar icon brightness.
// Light navigation bar appearance.
windowInsetsControllerCompat.setAppearanceLightNavigationBars(true);
break;
case LIGHT:
// Light navigation bar icon brightness.
// Dark navigation bar appearance.
windowInsetsControllerCompat.setAppearanceLightNavigationBars(false);
break;
}
}
if (systemChromeStyle.systemNavigationBarColor != null) {
window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor);
}
}
// You can't change the color of the navigation bar divider color until SDK 28.
if (systemChromeStyle.systemNavigationBarDividerColor != null && Build.VERSION.SDK_INT >= 28) {
window.setNavigationBarDividerColor(systemChromeStyle.systemNavigationBarDividerColor);
}
// You can't override the enforced contrast for a transparent navigation bar until SDK 29.
// This overrides the translucent scrim that may be placed behind 2/3 button navigation bars on
// SDK 29+ to ensure contrast is appropriate when using full screen layout modes like
// Edge to Edge.
if (systemChromeStyle.systemNavigationBarContrastEnforced != null
&& Build.VERSION.SDK_INT >= 29) {
window.setNavigationBarContrastEnforced(
systemChromeStyle.systemNavigationBarContrastEnforced);
}
currentTheme = systemChromeStyle;
}