Android 沉浸式状态栏

前提

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

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

情况一:只使用fitsSystemWindows属性

代码如下:

class ImmersionActivity1 : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_immersion1)
        window.statusBarColor = Color.TRANSPARENT
    }
}
<?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 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布局对这个属性进行了处理,其核心代码如下:

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 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 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 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>
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库识别状态栏区域的颜色,如果是浅色将状态栏图标的颜色改为黑色,否则改为白色。

添加依赖库

implementation 'androidx.palette:palette:1.0.0'

工具类:

/**
 * 状态栏工具类
 */
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
    }
}
/**
 * 屏幕工具类
 */
object ScreenUtils {

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

代码实现:

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)

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐