
1.前景
一個(gè)優(yōu)秀的應(yīng)用程序,往往各個(gè)方面或者UI深得用戶的喜愛,狀態(tài)欄圖標(biāo)也是其中的確定因素之一,當(dāng)你的AppBar使用著暗色調(diào)的顏色,并且狀態(tài)欄圖標(biāo)又使用著黑色主題的圖標(biāo)時(shí),不得不被用戶瘋狂吐槽,從而導(dǎo)致用戶的留存度下降,下面,我們來實(shí)現(xiàn)狀態(tài)欄圖標(biāo)的適配,讓你們開發(fā)的應(yīng)用增添一下色彩!
2.使用方法設(shè)置
1.暗色調(diào)狀態(tài)欄圖標(biāo)
//設(shè)置暗色調(diào)狀態(tài)欄圖標(biāo)
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 狀態(tài)欄顏色為透明
statusBarIconBrightness: Brightness.dark, // 狀態(tài)欄圖標(biāo)為暗色調(diào)
statusBarBrightness: Brightness.dark); // 狀態(tài)欄為暗色調(diào)
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);//設(shè)置生效
2.明色調(diào)狀態(tài)欄圖標(biāo)
//設(shè)置明亮色調(diào)狀態(tài)欄圖標(biāo)
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent, //狀態(tài)欄顏色為透明
statusBarIconBrightness: Brightness.light, // 狀態(tài)欄圖標(biāo)為明色調(diào)
statusBarBrightness: Brightness.light); // 狀態(tài)欄為明色調(diào)
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);//設(shè)置生效
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('暗色主題狀態(tài)欄圖標(biāo)')));
}
//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('亮色主題狀態(tài)欄圖標(biāo)'),
),
);
}
// main_page.dart
class MainPage extends StatefulWidget {
//..
}
class _MainPageState extends State<MainPage> {
// build
Scaffold(
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('暗色圖標(biāo)'),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => FirstPage()))),
RaisedButton(
child: Text('亮色圖標(biāo)'),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => SecondPage()))),
],
),
),
);
}
4.出現(xiàn)的問題:
使用上面調(diào)用的方法需要注意的是,當(dāng)main_page.dart含有一個(gè)自帶的AppBar時(shí),會(huì)導(dǎo)致設(shè)置不生效,具體的原因,我們可以看下面的另外一種方式設(shè)置狀態(tài)欄圖標(biāo)
3.使用AnnotatedRegion設(shè)置
1.什么是AnnotatedRegion?
用于將值注解到圖層樹中,我們來看一下這個(gè)類里面的方法
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,表示它只能生獨(dú)生子,并且它的創(chuàng)建需要使用RenderObject,參數(shù)上包含了一個(gè)泛型的值(用于圖層樹的查找),sized是否提供大小,更多的信息,我們只能在createRenderObject創(chuàng)建出的東西繼續(xù)查找
2.探索RenderAnnotatedRegion
這部分地方就開始涉及到畫圖層,查看以下該類的方法,如下
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) {
//重點(diǎn)關(guān)注的對(duì)象
final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
value,
size: sized ? size : null,
offset: sized ? offset : null,
);
//這個(gè)只是傳遞一個(gè)注釋圖層,不需要繪制任何的東西,只需要繼承paint即可
context.pushLayer(layer, super.paint, offset);
}
可以看到
-
RenderProxyBox繼承該類,說明他的大小是跟隨孩子的,如果設(shè)置sized為true,將包含大小和偏移量 -
value,sized都有getter,setter用于更新時(shí)進(jìn)行重繪. -
alwaysNeedsCompositing是否總是需要合成,后續(xù)文章會(huì)繼續(xù)分析這個(gè)參數(shù)的作用 -
paint方法,用于繪制圖層,我們重點(diǎn)關(guān)注這個(gè)
從上面看出,我們又得到一個(gè)新的成員AnnotatedRegionLayer
3.藏寶AnnotatedRegionLayer
該類主要用于把指定的值藏進(jìn)圖層中,方便別人挖寶??,全部的方法如下:
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默認(rèn)為false,在layer.find中無效的,可以忽略 - 2.
findAnnotations方法,主要的查找邏輯,layer.find會(huì)調(diào)用它,判斷傳入的localPosition,是否與圖層中的位置擊中,如果擊中的話,會(huì)把值add到result參數(shù)中 - 3.
debugFillPropertiesdebug過程中的配置信息
所以,我們需要查找哪個(gè)地方調(diào)用了layer.find,通過方法可以找到
4.挖寶RendererBinding
到這里,我們唯一能知道的是SystemChrome.setSystemUIOverlayStyle可以設(shè)置狀態(tài)欄,所以,我們通過查找調(diào)用的地方,查找到framework中view.dart調(diào)用了這個(gè)方法,代碼如下
void _updateSystemChrome() {
final Rect bounds = paintBounds;
//到達(dá)狀態(tài)欄中間的一半的距離
final Offset top = Offset(
bounds.center.dx,
_window.padding.top / 2.0,
);
//到達(dá)導(dǎo)航欄中間的一半的距離
final Offset bottom = Offset(
bounds.center.dx,
bounds.bottom - 1.0 - _window.padding.bottom / 2.0,
);
//查找到達(dá)狀態(tài)欄位置的圖層,這里會(huì)調(diào)用上面的findAnnotations查找
final SystemUiOverlayStyle? upperOverlayStyle = layer!.find<SystemUiOverlayStyle>(top);
SystemUiOverlayStyle? lowerOverlayStyle;
switch (defaultTargetPlatform) {
case TargetPlatform.android:
// 導(dǎo)航欄也一樣,只有安卓有這樣的導(dǎo)航欄
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);
}
}
代碼中會(huì)查找圖層中位置是在狀態(tài)欄一半,和導(dǎo)航欄一半包含的注釋,如果存在,則會(huì)重新設(shè)置新的樣式,調(diào)用_updateSystemChrome方法的時(shí)機(jī).
方法圖示:
可以看出,
SchedulerBinding.handleDrawFrame()注冊(cè)到window的繪制,當(dāng),window發(fā)生重繪時(shí),會(huì)最終調(diào)用到_updateSystemChrome()方法,導(dǎo)致即使你通過方法設(shè)置過狀態(tài)欄圖標(biāo),但下次重繪,如果能拿到layer里面存儲(chǔ)的設(shè)置狀態(tài)欄/導(dǎo)航欄信息時(shí),會(huì)重新覆蓋,也就是這個(gè)原因,導(dǎo)致了文本中1.4的問題,好了,今天的文章就到這里了哦,對(duì)看到這里的小伙伴說:謝謝觀看!下面來一個(gè)示例當(dāng)做最后的結(jié)尾。
5.滾動(dòng)列表顏色改變狀態(tài)欄圖標(biāo)
代碼如下:
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~