注册

Android 系统 Bar 沉浸式完美兼容方案(下)

续 Android 系统 Bar 沉浸式完美兼容方案(上)

完整代码

@file:Suppress("DEPRECATION")

package com.bytedance.heycan.systembar.activity

import android.app.Activity
import android.graphics.Color
import android.os.Build
import android.util.Size
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.bytedance.heycan.systembar.R

/**
* Created by dengchunguo on 2021/4/25
*/
fun Activity.setLightStatusBar(isLightingColorBoolean) {
   val window = this.window
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       if (isLightingColor) {
           window.decorView.systemUiVisibility =
               View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
      } else {
           window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
      }
  }
}

fun Activity.setLightNavigationBar(isLightingColorBoolean) {
   val window = this.window
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isLightingColor) {
       window.decorView.systemUiVisibility =
           window.decorView.systemUiVisibility or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.OView.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR else 0
  }
}

/**
* 必须在Activity的onCreate时调用
*/
fun Activity.immersiveStatusBar() {
   val view = (window.decorView as ViewGroup).getChildAt(0)
   view.addOnLayoutChangeListener { v________ ->
       val lp = view.layoutParams as FrameLayout.LayoutParams
       if (lp.topMargin > 0) {
           lp.topMargin = 0
           v.layoutParams = lp
      }
       if (view.paddingTop > 0) {
           view.setPadding(000view.paddingBottom)
           val content = findViewById<View>(android.R.id.content)
           content.requestLayout()
      }
  }

   val content = findViewById<View>(android.R.id.content)
   content.setPadding(000content.paddingBottom)

   window.decorView.findViewById(R.id.status_bar_view?View(window.context).apply {
       id = R.id.status_bar_view
       val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENTstatusHeight)
       params.gravity = Gravity.TOP
       layoutParams = params
      (window.decorView as ViewGroup).addView(this)

      (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
           override fun onChildViewAdded(parentView?childView?) {
               if (child?.id == android.R.id.statusBarBackground) {
                   child.scaleX = 0f
              }
          }

           override fun onChildViewRemoved(parentView?childView?) {
          }
      })
  }
   setStatusBarColor(Color.TRANSPARENT)
}

/**
* 必须在Activity的onCreate时调用
*/
fun Activity.immersiveNavigationBar(callback: (() -> Unit)? = null) {
   val view = (window.decorView as ViewGroup).getChildAt(0)
   view.addOnLayoutChangeListener { v________ ->
       val lp = view.layoutParams as FrameLayout.LayoutParams
       if (lp.bottomMargin > 0) {
           lp.bottomMargin = 0
           v.layoutParams = lp
      }
       if (view.paddingBottom > 0) {
           view.setPadding(0view.paddingTop00)
           val content = findViewById<View>(android.R.id.content)
           content.requestLayout()
      }
  }

   val content = findViewById<View>(android.R.id.content)
   content.setPadding(0content.paddingTop0-1)

   val heightLiveData = MutableLiveData<Int>()
   heightLiveData.value = 0
   window.decorView.setTag(R.id.navigation_height_live_dataheightLiveData)
   callback?.invoke()

   window.decorView.findViewById(R.id.navigation_bar_view?View(window.context).apply {
       id = R.id.navigation_bar_view
       val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENTheightLiveData.value ?0)
       params.gravity = Gravity.BOTTOM
       layoutParams = params
      (window.decorView as ViewGroup).addView(this)

       if (this@immersiveNavigationBar is FragmentActivity) {
           heightLiveData.observe(this@immersiveNavigationBar) {
               val lp = layoutParams
               lp.height = heightLiveData.value ?0
               layoutParams = lp
          }
      }

      (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
           override fun onChildViewAdded(parentView?childView?) {
               if (child?.id == android.R.id.navigationBarBackground) {
                   child.scaleX = 0f
                   bringToFront()

                   child.addOnLayoutChangeListener { __top_bottom____ ->
                       heightLiveData.value = bottom - top
                  }
              } else if (child?.id == android.R.id.statusBarBackground) {
                   child.scaleX = 0f
              }
          }

           override fun onChildViewRemoved(parentView?childView?) {
          }
      })
  }
   setNavigationBarColor(Color.TRANSPARENT)
}

/**
* 当设置了immersiveStatusBar时,如需使用状态栏,可调佣该函数
*/
fun Activity.fitStatusBar(fitBoolean) {
   val content = findViewById<View>(android.R.id.content)
   if (fit) {
       content.setPadding(0statusHeight0content.paddingBottom)
  } else {
       content.setPadding(000content.paddingBottom)
  }
}

fun Activity.fitNavigationBar(fitBoolean) {
   val content = findViewById<View>(android.R.id.content)
   if (fit) {
       content.setPadding(0content.paddingTop0navigationBarHeightLiveData.value ?0)
  } else {
       content.setPadding(0content.paddingTop0-1)
  }
   if (this is FragmentActivity) {
       navigationBarHeightLiveData.observe(this) {
           if (content.paddingBottom != -1) {
               content.setPadding(0content.paddingTop0it)
          }
      }
  }
}

val Activity.isImmersiveNavigationBarBoolean
   get() = window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION != 0

val Activity.statusHeightInt
   get() {
       val resourceId =
           resources.getIdentifier("status_bar_height""dimen""android")
       if (resourceId > 0) {
           return resources.getDimensionPixelSize(resourceId)
      }
       return 0
  }

val Activity.navigationHeightInt
   get() {
       return navigationBarHeightLiveData.value ?0
  }

val Activity.screenSizeSize
   get() {
       return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
           Size(windowManager.currentWindowMetrics.bounds.width(), windowManager.currentWindowMetrics.bounds.height())
      } else {
           Size(windowManager.defaultDisplay.widthwindowManager.defaultDisplay.height)
      }
  }

fun Activity.setStatusBarColor(colorInt) {
   val statusBarView = window.decorView.findViewById<View?>(R.id.status_bar_view)
   if (color == 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
       statusBarView?.setBackgroundColor(STATUS_BAR_MASK_COLOR)
  } else {
       statusBarView?.setBackgroundColor(color)
  }
}

fun Activity.setNavigationBarColor(colorInt) {
   val navigationBarView = window.decorView.findViewById<View?>(R.id.navigation_bar_view)
   if (color == 0 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
       navigationBarView?.setBackgroundColor(STATUS_BAR_MASK_COLOR)
  } else {
       navigationBarView?.setBackgroundColor(color)
  }
}

@Suppress("UNCHECKED_CAST")
val Activity.navigationBarHeightLiveDataLiveData<Int>
   get() {
       var liveData = window.decorView.getTag(R.id.navigation_height_live_dataas? LiveData<Int>
       if (liveData == null) {
           liveData = MutableLiveData()
           window.decorView.setTag(R.id.navigation_height_live_dataliveData)
      }
       return liveData
  }

val Activity.screenWidthInt get() = screenSize.width

val Activity.screenHeightInt get() = screenSize.height

private const val STATUS_BAR_MASK_COLOR = 0x7F000000

扩展

对话框适配

有时候需要通过 Dialog 来显示一个提示对话框、loading 对话框等,当显示一个对话框时,即使设置了 activity 为深色状态栏和导航栏文字颜色,这时候状态栏和导航栏的文字颜色又变成白色,如下所示:

9dc87d05a09b46dd9f596501a15dc186~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image

这是因为对 activity 设置的状态栏和导航栏颜色是作用 于 activity 的 window,而 dialog 和 activity 不是同一个 window,因此 dialog 也需要单独设置。

完整代码

@file:SuppressDEPRECATION )

package com.bytedance.heycan.systembar.dialog

import android.app.Dialog
import android.os.Build
import android.view.View
import android.view.ViewGroup

/**
* Created by dengchunguo on 2021/4/25
*/
fun Dialog.setLightStatusBar(isLightingColorBoolean) {
   val window = this.window ?return
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       if (isLightingColor) {
           window.decorView.systemUiVisibility =
               View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
      } else {
           window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
      }
  }
}

fun Dialog.setLightNavigationBar(isLightingColorBoolean) {
   val window = this.window ?return
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isLightingColor) {
       window.decorView.systemUiVisibility =
           window.decorView.systemUiVisibility or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.OView.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR else 0
  }
}

fun Dialog.immersiveStatusBar() {
   val window = this.window ?return
  (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
       override fun onChildViewAdded(parentView?childView?) {
           if (child?.id == android.R.id.statusBarBackground) {
               child.scaleX = 0f
          }
      }

       override fun onChildViewRemoved(parentView?childView?) {
      }
  })
}

fun Dialog.immersiveNavigationBar() {
   val window = this.window ?return
  (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
       override fun onChildViewAdded(parentView?childView?) {
           if (child?.id == android.R.id.navigationBarBackground) {
               child.scaleX = 0f
          } else if (child?.id == android.R.id.statusBarBackground) {
               child.scaleX = 0f
          }
      }

       override fun onChildViewRemoved(parentView?childView?) {
      }
  })
}

效果如下:

519ab1d088cb4ecf853cb86419fa1c78~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image

快速使用

Activity 沉浸式

immersiveStatusBar() // 沉浸式状态栏
immersiveNavigationBar() // 沉浸式导航栏

setLightStatusBar(true// 设置浅色状态栏背景(文字为深色)
setLightNavigationBar(true// 设置浅色导航栏背景(文字为深色)

setStatusBarColor(color// 设置状态栏背景色
setNavigationBarColor(color// 设置导航栏背景色

navigationBarHeightLiveData.observe(this) {
// 监听导航栏高度变化
}

Dialog 沉浸式

val dialog = Dialog(thisR.style.Heycan_SampleDialog)
dialog.setContentView(R.layout.dialog_loading)
dialog.immersiveStatusBar()
dialog.immersiveNavigationBar()
dialog.setLightStatusBar(true)
dialog.setLightNavigationBar(true)
dialog.show()

Demo 效果

88696764529545c9a233fd4dfea3e876~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image

可实现与 iOS 类似的页面沉浸式导航条效果:

6453830dc13e485c98a8d1fdccfeae53~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image

作者:字节跳动技术团队
来源:juejin.cn/post/7075578574362640421

0 个评论

要回复文章请先登录注册