Flutter深入分析状态栏图标适配

1.前景

一个优秀的应用程序,往往各个方面或者UI深得用户的喜爱,状态栏图标也是其中的确定因素之一,当你的AppBar使用着暗色调的颜色,并且状态栏图标又使用着黑色主题的图标时,不得不被用户疯狂吐槽,从而导致用户的留存度下降,下面,我们来实现状态栏图标的适配,让你们开发的应用增添一下色彩!

2.使用方法设置

1.暗色调状态栏图标

//设置暗色调状态栏图标
      SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
          statusBarColor: Colors.transparent, // 状态栏颜色为透明
          statusBarIconBrightness: Brightness.dark, // 状态栏图标为暗色调
          statusBarBrightness: Brightness.dark); // 状态栏为暗色调
      SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);//设置生效

2.明色调状态栏图标

//设置明亮色调状态栏图标
      SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
          statusBarColor: Colors.transparent, //状态栏颜色为透明
          statusBarIconBrightness: Brightness.light, // 状态栏图标为明色调
          statusBarBrightness: Brightness.light); // 状态栏为明色调
      SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);//设置生效

3.使用示例:

//first_page.dart
class FirstPage extends StatefulWidget {
//...
}
class _FirstPageState extends State<FirstPage> {
// initState
    SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle.dark;
    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
    
// build
    Scaffold(
        body: Container(alignment: Alignment.center, child: Text('暗色主题状态栏图标')));
}

//second_page.dart
class SecondPage extends StatefulWidget {
//..
}

class _SecondPageState extends State<SecondPage> {
//initState
    SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle.light;
    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);

 //build
    Scaffold(
      body: Container(
        alignment: Alignment.center,
        child: Text('亮色主题状态栏图标'),
      ),
    );
}
// main_page.dart
class MainPage extends StatefulWidget {
//..
}
class _MainPageState extends State<MainPage> {
// build
  Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
                child: Text('暗色图标'),
                onPressed: () => Navigator.of(context)
                    .push(MaterialPageRoute(builder: (_) => FirstPage()))),
            RaisedButton(
                child: Text('亮色图标'),
                onPressed: () => Navigator.of(context)
                    .push(MaterialPageRoute(builder: (_) => SecondPage()))),
          ],
        ),
      ),
  );
}

4.出现的问题:

使用上面调用的方法需要注意的是,当main_page.dart含有一个自带的AppBar时,会导致设置不生效,具体的原因,我们可以看下面的另外一种方式设置状态栏图标

3.使用AnnotatedRegion设置

1.什么是AnnotatedRegion?

用于将值注解到图层树中,我们来看一下这个类里面的方法


Dart客栈
class AnnotatedRegion<T extends Object> extends SingleChildRenderObjectWidget {
  const AnnotatedRegion({
    Key? key,
    required Widget child,
    required this.value,
    this.sized = true,
  }) : assert(value != null),
       assert(child != null),
       super(key: key, child: child);

  final T value;

  final bool sized;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderAnnotatedRegion<T>(value: value, sized: sized);
  }

  @override
  void updateRenderObject(BuildContext context, RenderAnnotatedRegion<T> renderObject) {
    renderObject
      ..value = value
      ..sized = sized;
  }
}

该类继承自SingleChildRenderObjectWidget,表示它只能生独生子,并且它的创建需要使用RenderObject,参数上包含了一个泛型的值(用于图层树的查找),sized是否提供大小,更多的信息,我们只能在createRenderObject创建出的东西继续查找

2.探索RenderAnnotatedRegion

这部分地方就开始涉及到画图层,查看以下该类的方法,如下


Dart客栈
class RenderAnnotatedRegion<T extends Object> extends RenderProxyBox {

  RenderAnnotatedRegion({
    required T value,
    required bool sized,
    RenderBox? child,
  }) : assert(value != null),
       assert(sized != null),
       _value = value,
       _sized = sized,
       super(child);

  T get value => _value;
  T _value;
  set value (T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    markNeedsPaint();
  }

  bool get sized => _sized;
  bool _sized;
  set sized(bool value) {
    if (_sized == value)
      return;
    _sized = value;
    markNeedsPaint();
  }

  @override
  final bool alwaysNeedsCompositing = true;

  @override
  void paint(PaintingContext context, Offset offset) {
//重点关注的对象
    final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
      value,
      size: sized ? size : null,
      offset: sized ? offset : null,
    );
//这个只是传递一个注释图层,不需要绘制任何的东西,只需要继承paint即可
    context.pushLayer(layer, super.paint, offset);
  }

可以看到

  • RenderProxyBox 继承该类,说明他的大小是跟随孩子的,如果设置sized为true,将包含大小和偏移量
  • value,sized都有getter,setter用于更新时进行重绘.
  • alwaysNeedsCompositing 是否总是需要合成,后续文章会继续分析这个参数的作用
  • paint方法,用于绘制图层,我们重点关注这个

从上面看出,我们又得到一个新的成员AnnotatedRegionLayer

3.藏宝AnnotatedRegionLayer

该类主要用于把指定的值藏进图层中,方便别人挖宝🤩,全部的方法如下:


Dart客栈
class AnnotatedRegionLayer<T extends Object> extends ContainerLayer {
  AnnotatedRegionLayer(
    this.value, {
    this.size,
    Offset? offset,
    this.opaque = false,
  }) : assert(value != null),
       assert(opaque != null),
       offset = offset ?? Offset.zero;

  final bool opaque;

  @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
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<T>('value', value));
    properties.add(DiagnosticsProperty<Size>('size', size, defaultValue: null));
    properties.add(DiagnosticsProperty<Offset>('offset', offset, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: false));
  }
}

分析代码:

  • 1.opaque默认为false,在layer.find中无效的,可以忽略
  • 2.findAnnotations方法,主要的查找逻辑,layer.find会调用它,判断传入的localPosition,是否与图层中的位置击中,如果击中的话,会把值addresult参数中
  • 3.debugFillPropertiesdebug过程中的配置信息

所以,我们需要查找哪个地方调用了layer.find,通过方法可以找到

4.挖宝RendererBinding

到这里,我们唯一能知道的是SystemChrome.setSystemUIOverlayStyle可以设置状态栏,所以,我们通过查找调用的地方,查找到frameworkview.dart调用了这个方法,代码如下

 void _updateSystemChrome() {
    final Rect bounds = paintBounds;
    //到达状态栏中间的一半的距离
    final Offset top = Offset(
      bounds.center.dx,
      _window.padding.top / 2.0,
    );
    //到达导航栏中间的一半的距离
    final Offset bottom = Offset(
      bounds.center.dx,
      bounds.bottom - 1.0 - _window.padding.bottom / 2.0,
    );
    
    //查找到达状态栏位置的图层,这里会调用上面的findAnnotations查找
    final SystemUiOverlayStyle? upperOverlayStyle = layer!.find<SystemUiOverlayStyle>(top);

    SystemUiOverlayStyle? lowerOverlayStyle;
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        // 导航栏也一样,只有安卓有这样的导航栏
        lowerOverlayStyle = layer!.find<SystemUiOverlayStyle>(bottom);
        break;
      case TargetPlatform.fuchsia:
      case TargetPlatform.iOS:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
        break;
    }
    
    if (upperOverlayStyle != null || lowerOverlayStyle != null) {
      final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle(
        statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
        statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
        statusBarColor: upperOverlayStyle?.statusBarColor,
        systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
        systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
        systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
      );
      SystemChrome.setSystemUIOverlayStyle(overlayStyle);
    }
  }

代码中会查找图层中位置是在状态栏一半,和导航栏一半包含的注释,如果存在,则会重新设置新的样式,调用_updateSystemChrome方法的时机.
方法图示:

image

可以看出,SchedulerBinding.handleDrawFrame()注册到window的绘制,当,window发生重绘时,会最终调用到_updateSystemChrome()方法,导致即使你通过方法设置过状态栏图标,但下次重绘,如果能拿到layer里面存储的设置状态栏/导航栏信息时,会重新覆盖,也就是这个原因,导致了文本中1.4的问题,好了,今天的文章就到这里了哦,对看到这里的小伙伴说:谢谢观看!下面来一个示例当做最后的结尾。

5.滚动列表颜色改变状态栏图标

image

代码如下:

  List<SystemUiOverlayStyle> uiOverlay = [
    SystemUiOverlayStyle.dark,
    SystemUiOverlayStyle.light,
  ];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: ListView.builder(
          itemBuilder: _buildItems,
          itemCount: 10,
        ));
  }
  Widget _buildItems(BuildContext context, int index) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      child: Container(
        height: 200,
        color: uiOverlay[index%2]==SystemUiOverlayStyle.dark?Colors.white:Colors.black,
      ),
      value: uiOverlay[index % 2],
      sized: true,
    );
  }

@rhyme_lph_2020.01.13_Dart客栈_end~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容