Android - 让视图文件显示到状态栏下,且不受底部导航虚拟按键影响
最新需要实现这个功能,琢磨了半天,网上也查找了一些资料,不过都是零零散散的,而且大部分是xml实现的,现在我用代码大致来实现一下这个功能。
第一步,获取状态栏高度,底部虚拟导航按键高度
/**
* 获取状态栏高度
*
* @param context 上下文对象
*/
@SuppressLint("PrivateApi")
@JvmStatic
fun getStatusBarHeight(context: Context): Int = try {
val c: Class<*>? = Class.forName("com.android.internal.R\$dimen")
val obj: Any? = c?.newInstance()
val field: Field? = c?.getField("status_bar_height")
if (obj != null && field != null) {
field.isAccessible = true
val x = Integer.parseInt(field.get(obj).toString())
context.resources.getDimensionPixelSize(x)
} else {
75
}
} catch (e1: Exception) {
Log.d("ApkUtil", "get status bar height fail")
e1.printStackTrace()
75
}
/**
* 获取底部导航栏高度
*
* @param context
* @return
*/
@JvmStatic
fun getNavigationBarHeight(context: Context): Int {
if (!isHasNavBar(context)) return 0
val resourceId: Int
val rid = context.resources.getIdentifier("config_showNavigationBar", "bool", "android")
return if (rid != 0) {
resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
context.resources.getDimensionPixelSize(resourceId)
} else {
0
}
}
/**
* 判断虚拟按键栏是否重写
*
* @return
*/
private val navBarOverride: String?
@SuppressLint("PrivateApi")
get() {
var sNavBarOverride: String? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val c = Class.forName("android.os.SystemProperties")
val m = c.getDeclaredMethod("get", String::class.java)
m.isAccessible = true
sNavBarOverride = m.invoke(null, "qemu.hw.mainkeys") as String
} catch (e: Throwable) {
}
}
return sNavBarOverride
}
/**
* 检查是否存在虚拟按键栏
*
* @param context
* @return
*/
@JvmStatic
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
fun isHasNavBar(context: Context): Boolean {
val res = context.resources
val resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android")
return when {
resourceId != 0 -> {
var hasNav = res.getBoolean(resourceId)
// check override flag
val sNavBarOverride = navBarOverride
if ("1" == sNavBarOverride) {
hasNav = false
} else if ("0" == sNavBarOverride) {
hasNav = true
}
hasNav
}
else -> // fallback
!ViewConfiguration.get(context).hasPermanentMenuKey()
}
}
第二步,处理Activity的Window的Flag属性
override fun onCreate(...){
window.requestFeature(Window.FEATURE_NO_TITLE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.BLACK
}
}
第三步,添加视图到Activity的RootView(DecorView)中
setContentView(
contentView,
WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
)
第四步,动态监听布局变化,动态设置导航栏高度
/** 根据导航栏高度判断页面布局 **/
private fun setContentViewLayoutParamsByNavigationBarHeight() {
val navigationBarHeight by lazy { ApkUtil.getNavigationBarHeight(this@PActivity) }
val lp: FrameLayout.LayoutParams?
if (navigationBarHolderView == null) {
navigationBarHolderView = View(this)
navigationBarHolderView?.setBackgroundColor(Color.BLACK)
lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, navigationBarHeight)
lp.gravity = Gravity.BOTTOM
[email protected](navigationBarHolderView, lp)
} else {
lp = navigationBarHolderView?.layoutParams as? FrameLayout.LayoutParams
lp?.height = navigationBarHeight
navigationBarHolderView?.layoutParams = lp
}
binder.root.setPadding(0, 0, 0, navigationBarHeight)
}
override fun onGlobalLayout() {
if (isContentViewAttachScreenTop)
setContentViewLayoutParamsByNavigationBarHeight()
}
override fun onResume() {
if (isContentViewAttachScreenTop)
rootView.viewTreeObserver.addOnGlobalLayoutListener(this)
super.onResume()
}
override fun onStop() {
super.onStop()
if (isContentViewAttachScreenTop) {
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
else
rootView.viewTreeObserver.removeGlobalOnLayoutListener(this)
}
}
先写成这个样子,后面我有时间会再来修改的!经过一段时间的使用,发现以上的代码并不能很好的解决问题,于是又改进了一套,这次使用的经过了考验,感觉不错!
open class PopcornActivity : AppCompatActivity() {
protected lateinit var ctx: Context
protected lateinit var activity: Activity
protected lateinit var rootView: FrameLayout
protected lateinit var binder: B
protected var imgBackground: ImageView? = null
/** 状态栏的高度 **/
protected val statusBarHeight by lazy { ApkUtil.getStatusBarHeight(this) }
/** ActionBar的高度 **/
protected val actionBarHeight by lazy { resources.getDimensionPixelSize(R.dimen.abc_action_bar_default_height_material) }
/** 布局文件是否顶到屏幕的顶端,即:状态栏是透明可见的,能显示布局文件, 且@ContentView中statusBarColor参数失效。默认值为false **/
protected open val isContentViewAttachScreenTop: Boolean = false
/** 标识参数,方式ViewTree多次添加GlobalListener **/
private val isSetContentViewAttachScreenTopTag: AtomicBoolean = AtomicBoolean(false)
/** 导航栏的颜色资源ID,默认为黑色[android.R.color.black] **/
protected var navigationBarColorResId: Int = android.R.color.black
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ctx = this; activity = this; rootView = window.decorView as FrameLayout
if (isContentViewAttachScreenTop) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = Color.TRANSPARENT
rootView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
}
initActivityUI() //初始化ActivityUI视图
EventBus.getDefault().register(this) //注册Activity
}
/** 初始化ActivityUI视图 **/
@SuppressLint("InlinedApi")
private fun initActivityUI() {
val value = ViewUtil.inflateLayoutId(this)
when {
value?.size == 3 -> {
navigationBarColorResId = value[2] //获取导航栏的颜色资源ID
binder = DataBindingUtil.setContentView(activity, value[0])
setToolbar() //设置Toolbar的相关属性
setImmersiveStatusBar(value[1]) //设置沉浸式状态栏
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP)
window.navigationBarColor =
ContextCompat.getColor(this, navigationBarColorResId)
}
else -> {
throw Exception("错误:Cannot find layoutId & StatusBarResId")
}
}
}
/** 针对有Toolbar对象的Activity,点击Toolbar返回图标后的操作 **/
protected open fun onNavigationBackClick() =
Router.back(this, enterAnimRes = R.anim.slide_in_left, exitAnimRes = R.anim.slide_out_right)
override fun onBackPressed() {
with(javaClass.getAnnotation(BackHandler::class.java)) {
if (this != null) {
finish()
overridePendingTransition(
when {
enterAnimResId < 0 || enterAnimResId == android.R.anim.slide_in_left -> R.anim.slide_in_left
else -> enterAnimResId
},
when {
exitAnimResId < 0 || exitAnimResId == android.R.anim.slide_out_right -> R.anim.slide_out_right
else -> exitAnimResId
}
)
return
}
}
super.onBackPressed()
}
/** 接收到通知关闭当前的页面的事件,默认为关闭页面 **/
@Subscribe
open fun onReceivedActivityFinishEvent(event: ActivityFinishEvent) = finish()
override fun onDestroy() {
EventBus.getDefault().unregister(this)
super.onDestroy()
}
/** 设置Toolbar对象 **/
private fun setToolbar() {
try {
val toolbarId = ApkUtil.getIdentifierId(this, "toolbar", "id")
when (toolbarId > 0) {
true -> {
val toolbar = findViewById(toolbarId)
toolbar?.let {
it.title = ""
setSupportActionBar(it)
it.setNavigationOnClickListener { onNavigationBackClick() }
}
}
else -> Log.i(javaClass.simpleName, "Toolbar is not exists!")
}
} catch (e: Exception) {
e.printStackTrace()
Log.i(
javaClass.simpleName,
"Something is erro, when the program execute a method called setToolbar!"
)
}
}
/**
* 设置背景图片资源
* @param imgUri 图片资源
*/
protected fun setBackgroundImage(imgUri: Any): Unit = setBackgroundImage(imgUri, null)
/**
* 设置背景图片资源
* @param imgUri 图片资源
* @param onClickListener 点击事件
*/
protected fun setBackgroundImage(imgUri: Any, onClickListener: View.OnClickListener?) {
if (imgBackground == null) {
imgBackground = ImageView(this)
rootView.addView(
imgBackground, 0,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
)
}
when (imgUri) {
is Drawable -> imgBackground?.setImageDrawable(imgUri)
is Int -> imgBackground?.setImageResource(imgUri)
is File -> {
imgBackground?.setImageURI(
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> FileProvider.getUriForFile(
this,
"$packageName.FileProvider",
imgUri
)
else -> Uri.fromFile(imgUri)
}
)
}
is Bitmap -> imgBackground?.setImageBitmap(imgUri)
is String -> ImageLoader.displayImage(this, uri = imgUri, target = imgBackground!!)
}
imgBackground?.scaleType = ImageView.ScaleType.CENTER_CROP
if (onClickListener != null)
imgBackground?.setOnClickListener(onClickListener)
}
}
/**謤
* 设置沉浸式状态栏
* @param colorResId 状态栏颜色
*/
@SuppressLint("ObsoleteSdkInt")
protected fun setImmersiveStatusBar(colorResId: Int) {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
val statusBarColor = ApkUtil.getColor(this, colorResId)
window.statusBarColor = statusBarColor //设置状态栏颜色
if (statusBarColor == Color.WHITE || statusBarColor == Color.TRANSPARENT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) rootView.systemUiVisibility =
rootView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
else window.setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
)
}
}
}