Flutter状态栏修改

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);
  }

注意事项

  1. 当同一个页面使用多个AnnotatedRegion时,从WidgetTree最末端开始查找,找到第一个AnnotatedRegion就使用这个, 而AppBar中会自己设置AnnotatedRegion,所以需要注意他们的层级关系是否冲突
  2. 一定不要忘记添加范型约束,否则不会生效(这里不会报错,某些场景下用起来会感觉没问题,一旦出现两个页面上下堆叠且它们的背景颜色和状态栏颜色色调不一致时就会出现明显的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;
  }
  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter中,可以通过使用SystemChrome类和SystemUiOverlay枚举来隐藏状态栏。首先,我们可以使用`SystemChrome.setEnabledSystemUIOverlays([])`来隐藏状态栏和底部按钮栏。例如,以下代码可以实现隐藏状态栏和底部按钮栏: ```dart import 'package:flutter/services.dart'; SystemChrome.setEnabledSystemUIOverlays([]); ``` 如果你只想隐藏状态栏而保留底部按钮栏,可以使用`SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom])`。例如,以下代码隐藏状态栏但保留底部按钮栏: ```dart import 'package:flutter/services.dart'; SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); ``` 如果你想显示状态栏和底部按钮栏,可以使用`SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values)`。例如,以下代码显示状态栏和底部按钮栏: ```dart import 'package:flutter/services.dart'; SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); ``` 在隐藏状态栏的同时,你还可以使用`SystemChrome.setSystemUIOverlayStyle`来设置状态栏的样式。例如,你可以设置状态栏为透明: ```dart import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( statusBarColor: Colors.transparent, ), ); ``` 请注意,上述代码只适用于Flutter应用程序中隐藏和显示状态栏的方法。如果你想在Android上隐藏状态栏,你可以使用`SystemChrome.setSystemUIOverlayStyle`来设置`statusBarColor`为透明。例如,以下代码可以在Android上隐藏状态栏: ```dart import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; if (Platform.isAndroid) { SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, ); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); } ``` 希望这些代码对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [flutter 隐藏状态栏和底部按钮](https://blog.csdn.net/NotesChapter/article/details/110406540)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [flutter-status-alert:显示类似Apple系统的自动隐藏状态警报。 非常适合在不中断用户流程的情况下通知用户](https://download.csdn.net/download/weixin_42162216/15048923)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值