Flutter使用setSystemUIOverlayStyle有问题?这篇文章告诉你怎么弄。

1,600 阅读2分钟

问题背景

在全面屏手机到来的时代,Android手机有虚拟按键导航和全面屏手势。

应用市场要求APP适配Android12,当我们的app设计时需要采用沉浸式的布局。 在没有设置SplashScreen时,能够正确的自然沉浸到StatusBar上。 但当使用了SplashScreen,参考文章《向应用添加闪屏页》

情况一

如果采用下列的初始化方式

import android.os.Build
import android.os.Bundle
import androidx.core.view.WindowCompat
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    // Aligns the Flutter view vertically with the window.
    WindowCompat.setDecorFitsSystemWindows(getWindow(), false)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      // Disable the Android splash screen fade out animation to avoid
      // a flicker before the similar frame is drawn in Flutter.
      splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView.remove() }
    }
    super.onCreate(savedInstanceState)
  }
}

就会发现显示出了默认的ActionBar。

解决办法

style.xml中修改Theme.App.Starting

<!--增加下面两行-->
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>

运行后效果如下图

情况二

如果启动动画需要自定义Activity做交互动画。需要运行

val splashScreen: SplashScreen = this.installSplashScreen()

来获取自定义时。情况会更加复杂。

就会发现StatusBar和NavigationBar没有沉浸。

解决办法

第一步

style.xml的修改

 <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <!--下面三个是关键属性-->
        <item name="android:windowLightNavigationBar">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:enforceNavigationBarContrast">false</item>
    </style>

修改了style.xml后发现,在Pixal的模拟器上,发现底部导航栏的颜色会随着后面的背景色自动切换深浅模式。但是在vivo,oppo,华为等手机在虚拟按键的模式上并不能自动切换深浅模式,需要手动的处理。

第二步

main.dart中设置SystemUiOverlayStyle

SystemUiOverlayStyle light = SystemUiOverlayStyle.dark.copyWith(
    systemNavigationBarIconBrightness: Brightness.dark,
    //使用的时候发现,在虚拟按键的手机上,如果是采用全透明颜色,虚拟按键上将会有个半透明的白色覆盖,达不到全屏沉浸效果,所以需要使用非0透明值的颜色。
    systemNavigationBarColor: Colors.white.withOpacity(0.1),
    systemNavigationBarContrastEnforced: true,
    statusBarColor: Colors.transparent);

void main() {
  runApp(const MyApp());
  SystemChrome.setSystemUIOverlayStyle(light);
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(light);
    return MaterialApp(
        title: 'Demo',
        theme: ThemeData(
          useMaterial3: true,
          primarySwatch: Colors.blue,
          appBarTheme: AppBarTheme(
          //AppBar会覆盖主题,建议和SystemChrome.setSystemUIOverlayStyle是同一style
          systemOverlayStyle: light,
          ),
        ),
        home: const HomePage());
  }
}

当使用的是二段式或者三段式虚拟导航键,在代码中systemNavigationBarColor: Colors.white.withOpacity(0.1),如果这里使用纯透明的颜色,navigationBarColor将会被设置成白色的半透明。

如果对app有要求,可以把颜色设置为0.1alpha的白色

Android8以下的兼容问题。

在Android 8的模拟器上,虚拟栏是这样的。

情况一如下图。

情况二如下图:

情况一设置了对应的style更换NavigationBarColor也没有效果,所以需要兼容Android 8以下的APP推荐使用情况二。