Android 沉浸式状态栏

TOC

Android 沉浸式状态栏

前提

给 Activity 或 Application 设置 NoActionBar 的主题并设置透明导航栏。

xml 复制代码
<style name="NoActionBarTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <!-- 设置状态栏颜色: -->
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

情况一:只使用fitsSystemWindows属性

代码如下:

kotlin 复制代码
class ImmersionActivity1 : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_immersion1)
        window.statusBarColor = Color.TRANSPARENT
    }
}
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/pink"
    android:fitsSystemWindows="true"
    tools:context=".immersion.ImmersionActivity1">

</FrameLayout>

效果如下:

说明:

状态栏部分没有颜色,说明只使用 fitsSystemWindows 属性无法达到沉浸式效果。

情况二:使用CoordinatorLayout+fitsSystemWindows

将 FrameLayout 替换为 CoordinatorLayout 布局。

代码如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/pink"
    android:fitsSystemWindows="true"
    tools:context=".immersion.ImmersionActivity1">

</androidx.coordinatorlayout.widget.CoordinatorLayout>

效果如下:

说明:

状态栏有颜色,说明沉浸式效果已经达到了。

fitsSystemWindows属性原理

为什么根布局是FrameLayout时,fitsSystemWindows属性不生效,而使用CoordinatorLayout布局时才能生效。

这是因为CoordinatorLayout布局对这个属性进行了处理,其核心代码如下:

java 复制代码
private void setupForInsets() {
    if (Build.VERSION.SDK_INT < 21) {
        return;
    }

    if (ViewCompat.getFitsSystemWindows(this)) {
        if (mApplyWindowInsetsListener == null) {
            mApplyWindowInsetsListener =
                new androidx.core.view.OnApplyWindowInsetsListener() {
                @Override
                public WindowInsetsCompat onApplyWindowInsets(View v,
                                                              WindowInsetsCompat insets) {
                    return setWindowInsets(insets);
                }
            };
        }
        
        // 核心代码
        ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
        // 核心代码
        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                              | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    } else {
        ViewCompat.setOnApplyWindowInsetsListener(this, null);
    }
}

情况三:在CoordinatorLayout中添加子控件

问题:

根布局为 CoordinatorLayout,子控件 Button 和 ImageView 没有延伸到状态栏区域。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/pink"
    android:fitsSystemWindows="true"
    tools:context=".immersion.ImmersionActivity3">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:scaleType="fitXY"
        android:src="@drawable/a" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

解决:

可以借助 CollapsingToolbarLayout 控件解决。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/pink"
    android:fitsSystemWindows="true"
    tools:context=".immersion.ImmersionActivity3">

    <com.google.android.material.appbar.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            android:scaleType="fitXY"
            android:src="@drawable/a" />

    </com.google.android.material.appbar.CollapsingToolbarLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

情况四:使用FrameLayout实现沉浸式效果

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frame_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".immersion.ImmersionActivity4">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@drawable/a" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello" />

</FrameLayout>
kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_immersion4)
    window.statusBarColor = Color.TRANSPARENT
    val frameLayout: FrameLayout = findViewById(R.id.frame_layout)
    val button: Button = findViewById(R.id.button)

    //沉浸式效果:
    frameLayout.systemUiVisibility =
        SYSTEM_UI_FLAG_LAYOUT_STABLE or SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    //状态栏遮挡问题:
    ViewCompat.setOnApplyWindowInsetsListener(button, object : OnApplyWindowInsetsListener {
        override fun onApplyWindowInsets(
            view: View,
            insets: WindowInsetsCompat
        ): WindowInsetsCompat {
            val layoutParams = view.layoutParams as FrameLayout.LayoutParams
            layoutParams.topMargin = insets.systemWindowInsetTop
            return insets
        }
    })
}

区分背景图颜色和状态栏图标颜色

当状态栏图标的颜色和背景图颜色非常接近时,会导致部分内容看不清的情况。

这事可以借助Palette库识别状态栏区域的颜色,如果是浅色将状态栏图标的颜色改为黑色,否则改为白色。

添加依赖库

arduino 复制代码
implementation 'androidx.palette:palette:1.0.0'

工具类:

kotlin 复制代码
/**
 * 状态栏工具类
 */
object StatusBarUtils {

    /**
     * 获取状态栏高度
     */
    fun getStatusBarHeight(context: Context): Int {
        var result = 0
        val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
        if (resourceId > 0) {
            result = context.resources.getDimensionPixelSize(resourceId)
        }
        return result
    }

    /**
     * 亮色状态栏,图片和文字是黑色的
     */
    fun setLightStatusBar(activity: Activity) {
        val flags = activity.window.decorView.systemUiVisibility
        activity.window.decorView.systemUiVisibility = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    }

    /**
     * 暗色状态栏,图片和文字是白色的
     */
    fun setDarkStatusBar(activity: Activity) {
        val flags =
        activity.window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        activity.window.decorView.systemUiVisibility =
        flags xor View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    }
}
kotlin 复制代码
/**
 * 屏幕工具类
 */
object ScreenUtils {

    /**
     * 获取屏幕宽度
     */
    fun getScreenWidth(context: Context): Int {
        val displayMetrics = context.resources.displayMetrics
        return displayMetrics.heightPixels
    }
}

代码实现:

kotlin 复制代码
class ImmersionActivity5 : BaseActivity() {
    private lateinit var imageView: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_immersion5)
        val btnDarkImage = findViewById<Button>(R.id.btn_dark_image)
        val btnLightImage = findViewById<Button>(R.id.btn_light_image)
        imageView = findViewById(R.id.imageView)
        btnDarkImage.setOnClickListener {
            setImageByResource(R.drawable.dark_image)
        }
        btnLightImage.setOnClickListener {
            setImageByResource(R.drawable.light_image)
        }

        setImageByResource(R.drawable.light_image)
    }

    private fun setImageByResource(@DrawableRes imageResource: Int) {
        val bitmap = BitmapFactory.decodeResource(resources, imageResource)
        imageView.setImageBitmap(bitmap)
        detectBitmapColor(bitmap)
    }

    /**
     * 检测Bitmap颜色
     */
    private fun detectBitmapColor(bitmap: Bitmap) {
        val colorCount = 5
        val left = 0
        val top = 0
        val right = ScreenUtils.getScreenWidth(this)
        val bottom = StatusBarUtils.getStatusBarHeight(this)

        Palette.from(bitmap)
            .maximumColorCount(colorCount)
            .setRegion(left, top, right, bottom)
            .generate(object : Palette.PaletteAsyncListener {
                override fun onGenerated(palette: Palette?) {
                    var mostPopularSwatch: Palette.Swatch? = null
                    if (palette != null) {
                        for (swatch in palette.swatches) {
                            if (mostPopularSwatch == null
                                || swatch.population > mostPopularSwatch.population
                            ) {
                                mostPopularSwatch = swatch
                            }
                        }
                    }
                    mostPopularSwatch?.let { swatch ->
                        val luminance = ColorUtils.calculateLuminance(swatch.rgb)
                        if (luminance < 0.5) {
                            StatusBarUtils.setDarkStatusBar(mActivity)
                        } else {
                            StatusBarUtils.setLightStatusBar(mActivity)
                        }
                    }
                }
            })
    }
}

效果:

点击"切换暗色图片"按钮:

点击"切换亮色图片"按钮:

源码下载

其他资料

优秀博客推荐

第三方框架推荐(ImmersionBar)

第三方框架推荐(UltimateBarX)

相关推荐
CANI_PLUS2 小时前
ESP32将DHT11温湿度传感器采集的数据上传到XAMPP的MySQL数据库
android·数据库·mysql
来来走走3 小时前
Flutter SharedPreferences存储数据基本使用
android·flutter
安卓开发者4 小时前
Android模块化架构深度解析:从设计到实践
android·架构
雨白4 小时前
HTTP协议详解(二):深入理解Header与Body
android·http
阿豪元代码5 小时前
深入理解 SurfaceFlinger —— 如何调试 SurfaceFlinger
android
阿豪元代码5 小时前
深入理解 SurfaceFlinger —— 概述
android
CV资深专家6 小时前
Launcher3启动
android
stevenzqzq7 小时前
glide缓存策略和缓存命中
android·缓存·glide
雅雅姐7 小时前
Android 16 的用户和用户组定义
android
没有了遇见7 小时前
Android ConstraintLayout 之ConstraintSet
android