Android原生中实现沉浸式状态栏很普遍。那在Flutter中如何实现呢。下面就大概总结一下。
开发环境如下:
[✓] Flutter (Channel stable, 1.22.6, on Mac OS X 10.14.5 18F132 darwin-x64,
locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 11.3.1)
[✓] Android Studio (version 4.1)
使用AndroidStudio新建一个Flutter项目,这里就不展开说了
然后打开Android部分的AndroidManifest文件,默认会有一个MainActivity的定义
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/splash_background"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
样式一:Activity的样式定义为LaunchTheme,主要就是App启动时避免出现出现白屏或黑屏
样式二:io.flutter.embedding.android.NormalTheme的样式,这个就是Android进程启动后指定给Activity的样式,Flutter UI初始化后,这个样式会一直作为Flutter UI的背景存在。
样式三:闪屏的图片设置io.flutter.embedding.android.SplashScreenDrawable,这个图片作为样式一和样式三的过渡。为了在Flutter UI展示之前继续显示Launch 屏幕,所以这个图片要和Launch的一样。
下面就直接贴一下样式的定义:
LaunchTheme, 使用Activity展示一个启动页面,当Flutter开始绘制时,自动移除这个样式
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!--Android 5.x开始需要关闭状态栏半透明,并把颜色设置透明,否则导航栏会呈现系统默认的浅灰色-->
<!--关闭状态栏半透明效果-->
<item name="android:windowTranslucentStatus">false</item>
<!--设置状态栏为透明-->
<item name="android:statusBarColor">@android:color/transparent</item>
<!--设置导航栏半透明-->
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:fitsSystemWindows">true</item>
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowFullscreen">true</item>
</style>
NormalTheme,跟LaunchTheme相比,就是没有不需要全屏,然后要换一个所有页面使用的背景,相比Launch可能是少一个logo的背景图,当然这个要看具体需求。
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!--Android 5.x开始需要关闭状态栏半透明,并把颜色设置透明,否则导航栏会呈现系统默认的浅灰色-->
<!--关闭状态栏半透明效果-->
<item name="android:windowTranslucentStatus">false</item>
<!--设置状态栏为透明-->
<item name="android:statusBarColor">@android:color/transparent</item>
<!--设置导航栏半透明-->
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:fitsSystemWindows">true</item>
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
如果需要让Flutter的页面都直接使用Normal中设置的背景图,那么需要在MainActivity中覆盖一下如下方法,让Widget的本身的背景是透明的。
override fun getTransparencyMode(): TransparencyMode {
return TransparencyMode.transparent
}
如果想让这个App都是透明的,也就是直接能看到桌面壁纸的话,需要覆盖如下方法。
override fun getBackgroundMode(): FlutterActivityLaunchConfigs.BackgroundMode {
return FlutterActivityLaunchConfigs.BackgroundMode.transparent;
}
但此方法不建议使用,官方文档中说会严重影响性能。
/**
* The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
* {@link BackgroundMode#transparent}.
*
* <p>The default background mode is {@link BackgroundMode#opaque}.
*
* <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
* {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
* FlutterTextureView} to support transparency. This choice has a non-trivial performance
* impact. A transparent background should only be used if it is necessary for the app design
* being implemented.
*
* <p>A {@code FlutterActivity} that is configured with a background mode of {@link
* BackgroundMode#transparent} must have a theme applied to it that includes the following
* property: {@code <item name="android:windowIsTranslucent">true</item>}.
*/
@NonNull
public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
以上就是Android端的设置,我们还需要再Flutter中进行相应的设置。
runApp(new MyApp());
//必须写在组件渲染(也就是runApp方法)之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。
if (defaultTargetPlatform == TargetPlatform.android) {
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.dark,
);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
//手机的状态栏默认为打开的
//判断是否为苹果手机。如果是,并且padding top不为0即为x系列
//其他系列关闭状态栏
if (MediaQuery.of(_context).padding.top == null ||
MediaQuery.of(_context).padding.top == 0) {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]);
}
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.dark,
);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
以上即为全部设置。后续有更好的方式再继续更新。
2021年07月13日更新:
关于状态栏背景色和文本颜色设置,分为两种情况
- 无AppBar的情况
直接在main.dart中配置SystemUiOverlayStyle即可
runApp(MyApp());
//必须放着runApp之后执行,在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light, //light:文本为白色,dark:文本为黑色
systemNavigationBarColor: Colors.white,
systemNavigationBarDividerColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.dark,
);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
- 有AppBar的情况
需要在widget中的appbar中进行配置
同无Appbar一样,先要配置状态栏透明
runApp(MyApp());
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
然后在appbar中进行配置
appBar: AppBar(
backgroundColor: Colors.transparent, //把appbar的背景色改成透明
elevation: 0, //取消appbar的阴影
brightness: Brightness.light, //需要注意这里,light:状态栏文字黑色,dark:状态栏文字白色
title: Text(widget.title),
),
body:
如果不需要状态栏(比如splash界面)
//全屏,不显示状态栏,也不现实底部虚拟键盘
SystemChrome.setEnabledSystemUIOverlays([]);
//想要控制它显示 :
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]); //top : bottom