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)

相关推荐
stevenzqzq4 小时前
android中dp和px的关系
android
一一Null6 小时前
Token安全存储的几种方式
android·java·安全·android studio
JarvanMo7 小时前
flutter工程化之动态配置
android·flutter·ios
时光少年9 小时前
Android 副屏录制方案
android·前端
时光少年9 小时前
Android 局域网NIO案例实践
android·前端
alexhilton10 小时前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
流浪汉kylin10 小时前
Android TextView SpannableString 如何插入自定义View
android
火柴就是我11 小时前
git rebase -i,执行 squash 操作 进行提交合并
android
你说你说你来说12 小时前
安卓广播接收器(Broadcast Receiver)的介绍与使用
android·笔记
你说你说你来说12 小时前
安卓Content Provider介绍及使用
android·笔记