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,是否与图层中的位置击中,如果击中的话,会把值add
到result
参数中 - 3.
debugFillProperties
debug过程中的配置信息
所以,我们需要查找哪个地方调用了layer.find
,通过方法可以找到
4.挖宝RendererBinding
到这里,我们唯一能知道的是SystemChrome.setSystemUIOverlayStyle
可以设置状态栏,所以,我们通过查找调用的地方,查找到framework
中view.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
方法的时机.
方法图示:
可以看出,
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~