Flutter状态栏(Android)解析,不同页面不同效果优雅实现

一. 状态栏交互逻辑

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-FlutterFlutterActivity关闭后,再次打开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;
   }
 }

源代码见GitHub

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

推荐阅读更多精彩内容