一. 状态栏交互逻辑
1.FlutterActivity默认设置
FlutterActivity初始化配置状态栏。
FlutterActivity-onCreate()-configureStatusBarForFullscreenFlutterExperience()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
Android版本大于等于Android5.0(API-21),默认设置状态栏阴影。
2.Flutter设置状态栏
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle style);
static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
//上次设置的style任务完成,不允许加入新的任务,只是修改style的值
if (_pendingStyle != null) {
_pendingStyle = style;
return;
}
//防止重复设置,PageA-设置light成功,PageB-设置light不执行
if (style == _latestStyle) {
return;
}
_pendingStyle = style;
scheduleMicrotask(() {
if (_pendingStyle != _latestStyle) {
//将style转成map,调用原生设置style
SystemChannels.platform.invokeMethod<void>(
'SystemChrome.setSystemUIOverlayStyle',
_pendingStyle!._toMap(),
);
//记录上次设置的style
_latestStyle = _pendingStyle;
}
_pendingStyle = null;
});
}
3.Android设置Style
//FlutterActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
super.onCreate(savedInstanceState);
//创建Delegate,里面包含Plugin
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
delegate.onRestoreInstanceState(savedInstanceState);
...
}
//FlutterActivityAndFragmentDelegate#onAttach(Context)
void onAttach(@NonNull Context context) {
...
//host为FlutterActivity
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
host.configureFlutterEngine(flutterEngine);
}
//FlutterActivity
public PlatformPlugin providePlatformPlugin(
@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this);
}
//PlatformPlugin构造方法
public PlatformPlugin(
Activity activity, PlatformChannel platformChannel, PlatformPluginDelegate delegate) {
this.activity = activity;
this.platformChannel = platformChannel;
//mPlatformMessageHandler负责处理Flutter传递的事件,中间省略部分赋值代码~
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
this.platformPluginDelegate = delegate;
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
mPlatformMessageHandler
源码
final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
new PlatformChannel.PlatformMessageHandler() {
@Override
public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
PlatformPlugin.this.playSystemSound(soundType);
}
@Override
public void vibrateHapticFeedback(
@NonNull PlatformChannel.HapticFeedbackType feedbackType) {
PlatformPlugin.this.vibrateHapticFeedback(feedbackType);
}
@Override
public void setPreferredOrientations(int androidOrientation) {
setSystemChromePreferredOrientations(androidOrientation);
}
@Override
public void setApplicationSwitcherDescription(
@NonNull PlatformChannel.AppSwitcherDescription description) {
setSystemChromeApplicationSwitcherDescription(description);
}
@Override
public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) {
setSystemChromeEnabledSystemUIOverlays(overlays);
}
@Override
public void showSystemUiMode(@NonNull PlatformChannel.SystemUiMode mode) {
setSystemChromeEnabledSystemUIMode(mode);
}
@Override
public void setSystemUiChangeListener() {
setSystemChromeChangeListener();
}
@Override
public void restoreSystemUiOverlays() {
restoreSystemChromeSystemUIOverlays();
}
//真实处理Flutter设置style的方法
//SystemChannels.platform.invokeMethod<void>(
// 'SystemChrome.setSystemUIOverlayStyle',
// _pendingStyle!._toMap(),
// );
@Override
public void setSystemUiOverlayStyle(
@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
}
@Override
public void popSystemNavigator() {
PlatformPlugin.this.popSystemNavigator();
}
@Override
public CharSequence getClipboardData(
@Nullable PlatformChannel.ClipboardContentFormat format) {
return PlatformPlugin.this.getClipboardData(format);
}
@Override
public void setClipboardData(@NonNull String text) {
PlatformPlugin.this.setClipboardData(text);
}
@Override
public boolean clipboardHasStrings() {
return PlatformPlugin.this.clipboardHasStrings();
}
};
setSystemChromeSystemUIOverlayStyle
源码如下,这块就是纯安卓代码了。
private void setSystemChromeSystemUIOverlayStyle(
PlatformChannel.SystemChromeStyle systemChromeStyle) {
Window window = activity.getWindow();
View view = window.getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat =
new WindowInsetsControllerCompat(window, view);
// 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.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
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;
}
4. 混合开发带来的问题
如果是混合开发,调用栈如下:Navtive-Flutter
,FlutterActivity
关闭后,再次打开FlutterActivity
,在Flutter
设置状态栏不生效。
Flutter可使用SystemChrome.restoreSystemUIOverlays();
,恢复最后一次设置的Style,源码如下:
static Future<void> restoreSystemUIOverlays() async {
await SystemChannels.platform.invokeMethod<void>(
'SystemChrome.restoreSystemUIOverlays',
null,
);
}
Android可在FlutterActivity
中调用updateSystemUiOverlays()
恢复最后一次设置的Style。源码如下:
//FlutterActivity
@Override
public void updateSystemUiOverlays() {
if (delegate != null) {
delegate.updateSystemUiOverlays();
}
}
//FlutterActivityAndFragmentDelegate
void updateSystemUiOverlays() {
if (platformPlugin != null) {
// TODO(mattcarroll): find a better way to handle the update of UI overlays than calling
// through to platformPlugin. We're implicitly entangling the Window, Activity,
// Fragment, and engine all with this one call.
platformPlugin.updateSystemUiOverlays();
}
}
//PlatformPlugin
public void updateSystemUiOverlays() {
activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
//上次设置的style
if (currentTheme != null) {
setSystemChromeSystemUIOverlayStyle(currentTheme);
}
}
二.Flutter不同页面设置不同Style实现
假如有如下逻辑:不同页面的状态栏配置不同。
PageA(light)-PageB(dark)
打开PageA
设置状态栏白色图标,打开PageB
设置状态栏黑色图标,返回到PageA
再次设置状态栏白色图标。
通过监听Flutter页面变化实现,需要使用命名路由:继承NavigatorObserver
,代码如下:
void main() {
runApp(MaterialApp(
...
routes: {
"/A": (context) => PageA(),
"/B": (context) => PageB(),
},
navigatorObservers: [RouteObserver()],
));
}
class RouteObserver extends NavigatorObserver {
final Map<String, SystemUiOverlayStyle> _map = {};
RouteObserver() {
_map["/"] = dark;
_map["/A"] = light;
_map["/B"] = dark;
}
///设置亮色状态栏和导航栏
///android 默认statusBarColor = Color(0x40000000),只对安卓生[L]及以上生效
final SystemUiOverlayStyle light =
SystemUiOverlayStyle.light.copyWith(statusBarColor: Colors.transparent);
///设置暗色状态栏和导航栏
///android 默认statusBarColor = Color(0x40000000),只对安卓生[L]及以上生效
final SystemUiOverlayStyle dark =
SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.transparent);
@override
void didPop(Route route, Route? previousRoute) {
_setSystemUIOverlayStyle(previousRoute, route);
}
@override
void didPush(Route route, Route? previousRoute) {
_setSystemUIOverlayStyle(route, previousRoute);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
_setSystemUIOverlayStyle(newRoute, oldRoute);
}
void _setSystemUIOverlayStyle(Route? toRoute, Route? formRoute) {
String? toName = _getRoutNameFromRoute(toRoute);
String? formName = _getRoutNameFromRoute(formRoute);
if (toName == null) {
return;
}
SystemUiOverlayStyle? toStyle = _map[toName];
SystemUiOverlayStyle? fromStyle = _map[formName];
if (toStyle != null && toStyle != fromStyle) {
//这里没有调用Flutter自带的方法是为了解决混合开发带来的问题
SystemChannels.platform.invokeMethod<void>(
'SystemChrome.setSystemUIOverlayStyle',
_style2Map(toStyle),
);
}
}
Map<String, dynamic> _style2Map(SystemUiOverlayStyle style) {
return <String, dynamic>{
'systemNavigationBarColor': style.systemNavigationBarColor?.value,
'systemNavigationBarDividerColor':
style.systemNavigationBarDividerColor?.value,
'systemStatusBarContrastEnforced': style.systemStatusBarContrastEnforced,
'statusBarColor': style.statusBarColor?.value,
'statusBarBrightness': style.statusBarBrightness?.toString(),
'statusBarIconBrightness': style.statusBarIconBrightness?.toString(),
'systemNavigationBarIconBrightness':
style.systemNavigationBarIconBrightness?.toString(),
'systemNavigationBarContrastEnforced':
style.systemNavigationBarContrastEnforced,
};
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
SystemChrome.restoreSystemUIOverlays();
}
String? _getRoutNameFromRoute(Route? route) {
if (route == null) {
return null;
}
RouteSettings routeSettings = route.settings;
return routeSettings.name;
}
}