Android 控制系统 Bar 沉浸式完美兼容方案
来源:动力 2024年10月23日 12:16
但是在亮色系统会 bar 基础上带入沉浸式后,在 8.0 至 9.0 系统会里,无线电则有深色无线电 icon 不终止,而 10.0 以上基本版能推断深色无线电 icon:
Android 8.0 亮色沉浸式亮色无线电则有
Android 10.0 亮色沉浸式亮色无线电则有
原因统计分析原因一:沉浸式下未能新设着重色查看程式码断定新设状态则有和无线电则有着重色时,是很难为沉浸式的:
原因二:未能全紫色无线电则有当新设无线电则有为紫(Color.TRANSPARENT)时,无线电则有都会换成半紫色,当新设其他色,则是出现异常的,例如新设色为 0x700F7FFF,推断视觉效果如下:
Android 10.0 沉浸式无线电则有
为什么都会用到这个原因呢,通过调试转到程式码,断定 activity 的 onApplyThemeResource 方法有里有一个命题:
// Get the primary color and update the TaskDescription for this activityTypedArray a = theme.obtainStyledAttributes( com.android.internal.R.styleable.ActivityTaskDescription);if (mTaskDescription.getPrimaryColor() == 0) { int colorPrimary = a.getColor( com.android.internal.R.styleable.ActivityTaskDescription_colorPrimary, 0); if (colorPrimary != 0 && Color.alpha(colorPrimary) == 0xFF) { mTaskDescription.setPrimaryColor(colorPrimary); }}不一定如果新设的无线电则有色为 0(可称紫色)时,将都会为其修正为内置的色:ActivityTaskDescription_colorPrimary,因此就都会用到红色蒙层视觉效果。
原因三:亮色系统会 bar 基本版差异性通过查看程式码断定,与新设状态则有和无线电则有着重色相近,新设无线电则有 icon 色也是很难为沉浸式:
应付沉浸式相容性性原因对于原因二未能全紫色无线电则有,由上述原因统计分析里的标识符可以看得出,当且极少当新设的无线电则有色为可称紫色时(0),才都会可逆为半紫色的蒙层。那么,我们可以将可称紫色这种原因修正色为 0x01000000,这样也能达到相近可称紫色的视觉效果:
对于原因一,很难通过如前所述方式则来进行沉浸式下的系统会 bar 着重色新设。而对于原因三,通过如前所述方式则并不需要分别对各个基本版来进行适配,对于国外智能手机来说,适配可玩性格外大。
为认识到决相容性性原因,以及格外好的管理者状态则有和无线电则有,我们是否能自己借助于状态则有和无线电则有的着重 View 呢?
通过 Layout Inspector 可以看得出,无线电则有和状态则有本质上也是一个 view:
在 activity 创设的时候,都会创设两个 view(nigationBarBackground 和 statusBarBackground),将其加到 decorView 里,从而可以压制状态则有的色。那么,是否能把系统会的这两个 view 隐匿大大的,加到可选的 view 呢?
因此,为了大幅提高相容性性,以及格外好的管理者状态则有和无线电则有,我们可以将系统会的 nigationBarBackground 和 statusBarBackground 隐匿大大的,加到可选的 view,而不于是又通过 FLAG_TRANSLUCENT_STATUS 和 FLAG_TRANSLUCENT_NAVIGATION 来新设。
借助于沉浸式状态则有移除可选的状态则有。通过创设一个 view ,让其离地等于状态则有的离地,并将其移除到 decorView 里:View(window.context).apply {id = R.id.status_bar_view val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, statusHeight) params.grity = Grity.TOP layoutParams = params (window.decorView as ViewGroup).addView(this)}隐匿系统会的状态则有。由于 activity 在 onCreate 时,并从未创设状态则有的 view(statusBarBackground),因此未能同样将其隐匿。这里可以通过对 decorView 移除 OnHierarchyChangeListener NSA来捕捉到到 statusBarBackground:(window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { }})忽略:这里将 child 的 scaleX 设为 0 即可将其隐匿大大的,那么为什么很难新设 visibility 为 GONE 呢?这是因为后续在应用主题时(onApplyThemeResource),系统会都会将 visibility 又重新新设为 VISIBLE。
隐匿此后,半紫色的状态则有不推断,但是上方都会用到印出:
通过 Layout Inspector 断定,decorView 的第一个元素(章节 view )都会普遍存在一个 padding:
因此,可以通过新设 paddingTop 为 0 将其去除:
val view = (window.decorView as ViewGroup).getChildAt(0)view.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> if (view.paddingTop> 0) { view.setPadding(0, 0, 0, view.paddingBottom) val content = findViewById(android.R.id.content) content.requestLayout() }}忽略:这里并不需要NSA view 的 layout 叠加,否则只有一开始新设则后面又被修正了。
借助于沉浸式无线电则有无线电则有的可选与状态则有相近,不过都会普遍存在一些差异性。先为创设一个可选 view 将其移除到 decorView 里,然后把本来系统会的 nigationBarBackground 隐匿:
window.decorView.findViewById(R.id.nigation_bar_view) ?: View(window.context).apply {id = R.id.nigation_bar_view val resourceId = resources.getIdentifier( nigation_bar_height , dimen , android ) val nigationBarHeight = if (resourceId> 0) resources.getDimensionPixelSize(resourceId) else 0 val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, nigationBarHeight) params.grity = Grity.BOTTOM layoutParams = params (window.decorView as ViewGroup).addView(this) (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.nigationBarBackground) { child.scaleX = 0f } else if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { } })}忽略:这里 onChildViewAdded 方法有里,因为只能新设一次 OnHierarchyChangeListener ,并不需要同时权衡状态则有和无线电则有。
通过这个方式则,能将无线电则有格外换为可选的 view ,但是普遍存在一个原因,由于 nigationBarHeight 是一般而言的,如果普通用户读取了无线电则有的型式,于是又回到 app 时,无线电则有的离地不都会重新调整。为了让无线电则有看的似乎,新设其色为 0x7F00FF7F:
从图里可以看得出,无线电则有读取此后离地从未发生叠加。为认识到决这个原因,并不需要通过对 nigationBarBackground 新设 OnLayoutChangeListener 来NSA无线电则有离地的叠加,并通过 liveData 关联到 view 里,标识符借助于如下:
val heightLiveData = MutableLiveData()heightLiveData.value = 0window.decorView.setTag(R.id.nigation_height_live_data, heightLiveData)val nigationBarView = window.decorView.findViewById(R.id.nigation_bar_view) ?: View(window.context).apply { id = R.id.nigation_bar_view val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, heightLiveData.value ?: 0) params.grity = Grity.BOTTOM layoutParams = params (window.decorView as ViewGroup).addView(this) if (this@immersiveNigationBar is FragmentActivity) { heightLiveData.observe(this@immersiveNigationBar) { val lp = layoutParams lp.height = heightLiveData.value ?: 0 layoutParams = lp } } (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.nigationBarBackground) { child.scaleX = 0f child.addOnLayoutChangeListener { _, _, top, _, bottom, _, _, _, _ -> heightLiveData.value = bottom - top } } else if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { } })}通过上面方式则,可以应付读取无线电则有型式后可选的无线电则有离地原因:
基本标识符@file:Suppress("DEPRECATION")package com.bytedance.heycan.systembar.activityimport android.app.Activityimport android.graphics.Colorimport android.os.Buildimport android.util.Sizeimport android.view.Grityimport android.view.Viewimport android.view.ViewGroupimport android.view.WindowManagerimport android.widget.FrameLayoutimport androidx.fragment.app.FragmentActivityimport androidx.lifecycle.LiveDataimport androidx.lifecycle.MutableLiveDataimport com.bytedance.heycan.systembar.R/** * Created by dengchunguo on 2021/4/25 */fun Activity.setLightStatusBar(isLightingColor: Boolean) { 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.setLightNigationBar(isLightingColor: Boolean) { 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.O) View.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(0, 0, 0, view.paddingBottom) val content = findViewById(android.R.id.content) content.requestLayout() } } val content = findViewById(android.R.id.content) content.setPadding(0, 0, 0, content.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_PARENT, statusHeight) params.grity = Grity.TOP layoutParams = params (window.decorView as ViewGroup).addView(this) (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { } }) } setStatusBarColor(Color.TRANSPARENT)}/** * 必需在Activity的onCreate时函数调用 */fun Activity.immersiveNigationBar(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(0, view.paddingTop, 0, 0) val content = findViewById(android.R.id.content) content.requestLayout() } } val content = findViewById(android.R.id.content) content.setPadding(0, content.paddingTop, 0, -1) val heightLiveData = MutableLiveData() heightLiveData.value = 0 window.decorView.setTag(R.id.nigation_height_live_data, heightLiveData) callback?.invoke() window.decorView.findViewById(R.id.nigation_bar_view) ?: View(window.context).apply { id = R.id.nigation_bar_view val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, heightLiveData.value ?: 0) params.grity = Grity.BOTTOM layoutParams = params (window.decorView as ViewGroup).addView(this) if (this@immersiveNigationBar is FragmentActivity) { heightLiveData.observe(this@immersiveNigationBar) { val lp = layoutParams lp.height = heightLiveData.value ?: 0 layoutParams = lp } } (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.nigationBarBackground) { 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(parent: View?, child: View?) { } }) } setNigationBarColor(Color.TRANSPARENT)}/** * 当新设了immersiveStatusBar时,如需可用状态则有,固定式佣该表达式 */fun Activity.fitStatusBar(fit: Boolean) { val content = findViewById(android.R.id.content) if (fit) { content.setPadding(0, statusHeight, 0, content.paddingBottom) } else { content.setPadding(0, 0, 0, content.paddingBottom) }}fun Activity.fitNigationBar(fit: Boolean) { val content = findViewById(android.R.id.content) if (fit) { content.setPadding(0, content.paddingTop, 0, nigationBarHeightLiveData.value ?: 0) } else { content.setPadding(0, content.paddingTop, 0, -1) } if (this is FragmentActivity) { nigationBarHeightLiveData.observe(this) { if (content.paddingBottom != -1) { content.setPadding(0, content.paddingTop, 0, it) } } }}val Activity.isImmersiveNigationBar: Boolean get() = window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION != 0val Activity.statusHeight: Int get() { val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") if (resourceId> 0) { return resources.getDimensionPixelSize(resourceId) } return 0 }val Activity.nigationHeight: Int get() { return nigationBarHeightLiveData.value ?: 0 }val Activity.screenSize: Size get() { return if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.R) { Size(windowManager.currentWindowMetrics.bounds.width(), windowManager.currentWindowMetrics.bounds.height()) } else { Size(windowManager.defaultDisplay.width, windowManager.defaultDisplay.height) } }fun Activity.setStatusBarColor(color: Int) { val statusBarView = window.decorView.findViewById(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.setNigationBarColor(color: Int) { val nigationBarView = window.decorView.findViewById(R.id.nigation_bar_view) if (color == 0 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { nigationBarView?.setBackgroundColor(STATUS_BAR_MASK_COLOR) } else { nigationBarView?.setBackgroundColor(color) }}@Suppress("UNCHECKED_CAST")val Activity.nigationBarHeightLiveData: LiveData get() { var liveData = window.decorView.getTag(R.id.nigation_height_live_data) as? LiveData if (liveData == null) { liveData = MutableLiveData() window.decorView.setTag(R.id.nigation_height_live_data, liveData) } return liveData }val Activity.screenWidth: Int get() = screenSize.widthval Activity.screenHeight: Int get() = screenSize.heightprivate const val STATUS_BAR_MASK_COLOR = 0x7F000000扩展管理者器适配有时候并不需要通过 Dialog 来推断一个提示管理者器、loading 管理者器等,当推断一个管理者器时,即使新设了 activity 为深色状态则有和无线电则有字词色,这时候状态则有和无线电则有的字词色又换成黄色,如下标明:
这是因为对 activity 新设的状态则有和无线电则有色是作用 于 activity 的 window,而 dialog 和 activity 不是同一个 window,因此 dialog 也并不需要单独新设。
基本标识符@file:Suppress( DEPRECATION )package com.bytedance.heycan.systembar.dialogimport android.app.Dialogimport android.os.Buildimport android.view.Viewimport android.view.ViewGroup/** * Created by dengchunguo on 2021/4/25 */fun Dialog.setLightStatusBar(isLightingColor: Boolean) { 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.setLightNigationBar(isLightingColor: Boolean) { 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.O) View.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(parent: View?, child: View?) { if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { } })}fun Dialog.immersiveNigationBar() { val window = this.window ?: return (window.decorView as ViewGroup).setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewAdded(parent: View?, child: View?) { if (child?.id == android.R.id.nigationBarBackground) { child.scaleX = 0f } else if (child?.id == android.R.id.statusBarBackground) { child.scaleX = 0f } } override fun onChildViewRemoved(parent: View?, child: View?) { } })}视觉效果如下:
快速可用Activity 沉浸式immersiveStatusBar() // 沉浸式状态则有immersiveNigationBar() // 沉浸式无线电则有setLightStatusBar(true) // 新设灰黄色状态则有着重(字词为深色)setLightNigationBar(true) // 新设灰黄色无线电则有着重(字词为深色)setStatusBarColor(color) // 新设状态则有着重色setNigationBarColor(color) // 新设无线电则有着重色nigationBarHeightLiveData.observe(this) { // NSA无线电则有离地叠加}Dialog 沉浸式val dialog = Dialog(this, R.style.Heycan_SampleDialog)dialog.setContentView(R.layout.dialog_loading)dialog.immersiveStatusBar()dialog.immersiveNigationBar()dialog.setLightStatusBar(true)dialog.setLightNigationBar(true)dialog.show()Demo 视觉效果可借助于与 iOS 相近的页面沉浸式无线电条视觉效果:
转到我们我们是字节跳动摄影机他的团队,现有技术开发包括剪映、CapCut、轻颜、醒图、Faceu 在内的多款商品,业务覆盖多元化摄影机艺术创作场景,日和 2021 年 6 年底,剪映、轻颜相机、CapCut 等多次登山队国外外 APP Store 完全免费应用流行榜第一,并继续保持高速上涨。转到我们,一起打造全球最受普通用户欢迎的摄影机艺术创作商品。
社招派送链接:
校招派送链接:
认识到格外多岗位详情
。九江男科角膜炎用什么眼药水呢
膝关节疼痛用什么药治果好
无锡装修
关节畸形是什么原因造成的
肠炎宁片治疗拉肚子有用吗
什么血糖仪家用比较好
康恩贝肠炎宁颗粒儿童用量
什么牌子的血糖仪好
胃酸过多能吃金奥康吗
-
青岛啤酒股份(00168):于竹明复职执行董事
智通电视新闻APP即日,青岛啤酒股份00168发布公告,该公司执行常务董事于竹明因已届退休年龄,于2022年6月末21日向公司执委会审核辞职申请者,离任公司执行常务董事、执委会战略与外资理事