一. 狀態(tài)欄交互邏輯
1.FlutterActivity默認(rèn)設(shè)置
FlutterActivity初始化配置狀態(tài)欄腺兴。
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)坡垫,默認(rèn)設(shè)置狀態(tài)欄陰影。
2.Flutter設(shè)置狀態(tài)欄
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle style);
static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
//上次設(shè)置的style任務(wù)完成阴汇,不允許加入新的任務(wù),只是修改style的值
if (_pendingStyle != null) {
_pendingStyle = style;
return;
}
//防止重復(fù)設(shè)置节槐,PageA-設(shè)置light成功搀庶,PageB-設(shè)置light不執(zhí)行
if (style == _latestStyle) {
return;
}
_pendingStyle = style;
scheduleMicrotask(() {
if (_pendingStyle != _latestStyle) {
//將style轉(zhuǎn)成map,調(diào)用原生設(shè)置style
SystemChannels.platform.invokeMethod<void>(
'SystemChrome.setSystemUIOverlayStyle',
_pendingStyle!._toMap(),
);
//記錄上次設(shè)置的style
_latestStyle = _pendingStyle;
}
_pendingStyle = null;
});
}
3.Android設(shè)置Style
//FlutterActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
super.onCreate(savedInstanceState);
//創(chuàng)建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構(gòu)造方法
public PlatformPlugin(
Activity activity, PlatformChannel platformChannel, PlatformPluginDelegate delegate) {
this.activity = activity;
this.platformChannel = platformChannel;
//mPlatformMessageHandler負(fù)責(zé)處理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();
}
//真實(shí)處理Flutter設(shè)置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. 混合開發(fā)帶來的問題
如果是混合開發(fā)咆蒿,調(diào)用棧如下:Navtive-Flutter
,FlutterActivity
關(guān)閉后蚂子,再次打開FlutterActivity
沃测,在Flutter
設(shè)置狀態(tài)欄不生效。
Flutter可使用SystemChrome.restoreSystemUIOverlays();
食茎,恢復(fù)最后一次設(shè)置的Style蒂破,源碼如下:
static Future<void> restoreSystemUIOverlays() async {
await SystemChannels.platform.invokeMethod<void>(
'SystemChrome.restoreSystemUIOverlays',
null,
);
}
Android可在FlutterActivity
中調(diào)用updateSystemUiOverlays()
恢復(fù)最后一次設(shè)置的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);
//上次設(shè)置的style
if (currentTheme != null) {
setSystemChromeSystemUIOverlayStyle(currentTheme);
}
}
二.Flutter不同頁面設(shè)置不同Style實(shí)現(xiàn)
假如有如下邏輯:不同頁面的狀態(tài)欄配置不同别渔。
PageA(light)-PageB(dark)
打開PageA
設(shè)置狀態(tài)欄白色圖標(biāo)附迷,打開PageB
設(shè)置狀態(tài)欄黑色圖標(biāo),返回到PageA
再次設(shè)置狀態(tài)欄白色圖標(biāo)哎媚。
通過監(jiān)聽Flutter頁面變化實(shí)現(xiàn)喇伯,需要使用命名路由:繼承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;
}
///設(shè)置亮色狀態(tài)欄和導(dǎo)航欄
///android 默認(rèn)statusBarColor = Color(0x40000000)拨与,只對安卓生[L]及以上生效
final SystemUiOverlayStyle light =
SystemUiOverlayStyle.light.copyWith(statusBarColor: Colors.transparent);
///設(shè)置暗色狀態(tài)欄和導(dǎo)航欄
///android 默認(rèn)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) {
//這里沒有調(diào)用Flutter自帶的方法是為了解決混合開發(fā)帶來的問題
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;
}
}