-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathSystemUiUtils.kt
More file actions
271 lines (240 loc) · 9.23 KB
/
SystemUiUtils.kt
File metadata and controls
271 lines (240 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package com.reactnativenavigation.utils
import android.app.Activity
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import kotlin.math.abs
import kotlin.math.ceil
object SystemUiUtils {
private const val STATUS_BAR_HEIGHT_M = 24
internal const val STATUS_BAR_HEIGHT_TRANSLUCENCY = 0.65f
private var statusBarHeight = -1
const val DEFAULT_NAV_BAR_COLOR = Color.BLACK
private const val THREE_BUTTON_NAV_BAR_OPACITY = 0.8f
private var statusBarBackgroundView: View? = null
private var navBarBackgroundView: View? = null
private var isEdgeToEdgeActive = false
private var isThreeButtonNav = false
@JvmStatic
fun getStatusBarHeight(activity: Activity?): Int {
val res = if (statusBarHeight > 0) {
statusBarHeight
} else {
statusBarHeight = activity?.let {
val rectangle = Rect()
val window: Window = activity.window
window.decorView.getWindowVisibleDisplayFrame(rectangle)
val statusBarHeight: Int = rectangle.top
val contentView = window.findViewById<View>(Window.ID_ANDROID_CONTENT)
contentView?.let {
val contentViewTop = contentView.top
abs(contentViewTop - statusBarHeight)
}
} ?: STATUS_BAR_HEIGHT_M
statusBarHeight
}
return res
}
@JvmStatic
fun saveStatusBarHeight(height: Int) {
statusBarHeight = height
}
@JvmStatic
fun getStatusBarHeightDp(activity: Activity?): Int {
return UiUtils.pxToDp(activity, getStatusBarHeight(activity).toFloat()).toInt()
}
// region Setup
/**
* Initializes view-based system bar backgrounds for edge-to-edge.
* Call from Activity.onPostCreate after the navigator content layout is set.
*
* Status bar: reuses the system's android:id/statusBarBackground DecorView child.
* Navigation bar: creates a view in [contentLayout] sized by WindowInsets,
* since the system's navigationBarBackground is not available with EdgeToEdge.
*
* Both fall back to deprecated window APIs when the views are unavailable.
*/
@JvmStatic
fun setupSystemBarBackgrounds(activity: Activity, contentLayout: ViewGroup) {
setupStatusBarBackground(activity)
setupNavigationBarBackground(contentLayout)
}
private fun setupStatusBarBackground(activity: Activity) {
if (statusBarBackgroundView != null) return
val sbView = activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)
if (sbView != null) {
sbView.setBackgroundColor(Color.BLACK)
statusBarBackgroundView = sbView
}
}
private fun setupNavigationBarBackground(contentLayout: ViewGroup) {
if (navBarBackgroundView != null) return
val view = View(contentLayout.context).apply {
setBackgroundColor(Color.BLACK)
}
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, 0, Gravity.BOTTOM
)
contentLayout.addView(view, params)
navBarBackgroundView = view
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val navBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
val tappableHeight = insets.getInsets(WindowInsetsCompat.Type.tappableElement()).bottom
val wasThreeButton = isThreeButtonNav
isEdgeToEdgeActive = navBarHeight > 0
isThreeButtonNav = tappableHeight > 0
if (isThreeButtonNav != wasThreeButton) {
v.setBackgroundColor(getDefaultNavBarColor())
}
val lp = v.layoutParams
if (lp.height != navBarHeight) {
lp.height = navBarHeight
v.layoutParams = lp
}
insets
}
view.requestApplyInsets()
}
/**
* Returns the default navigation bar color, applying 80% opacity for 3-button navigation.
* Gesture navigation gets a fully opaque color since the bar is minimal.
*/
@JvmStatic
fun getDefaultNavBarColor(): Int {
if (!isThreeButtonNav) return DEFAULT_NAV_BAR_COLOR
val alpha = (THREE_BUTTON_NAV_BAR_OPACITY * 255).toInt()
return Color.argb(alpha, Color.red(DEFAULT_NAV_BAR_COLOR), Color.green(DEFAULT_NAV_BAR_COLOR), Color.blue(DEFAULT_NAV_BAR_COLOR))
}
/**
* Clears references to system bar background views.
* Call from Activity.onDestroy to avoid leaking views across activity recreation.
*/
@JvmStatic
fun tearDown() {
statusBarBackgroundView = null
navBarBackgroundView = null
isEdgeToEdgeActive = false
isThreeButtonNav = false
statusBarHeight = -1
}
// endregion
// region Status Bar
@JvmStatic
fun setStatusBarColorScheme(window: Window?, view: View, isDark: Boolean) {
window?.let {
WindowInsetsControllerCompat(window, view).isAppearanceLightStatusBars = isDark
}
}
@JvmStatic
fun setStatusBarTranslucent(window: Window?) {
getStatusBarColor(window)?.let { currentColor ->
setStatusBarColor(window, currentColor, true)
}
}
@JvmStatic
fun isTranslucent(window: Window?): Boolean {
val color = getStatusBarColor(window) ?: return false
return Color.alpha(color) < 255
}
@JvmStatic
fun clearStatusBarTranslucency(window: Window?) {
getStatusBarColor(window)?.let { currentColor ->
setStatusBarColor(window, currentColor, false)
}
}
@JvmStatic
fun setStatusBarColor(window: Window?, @ColorInt color: Int, translucent: Boolean) {
val colorAlpha = Color.alpha(color)
val alpha = if (translucent && colorAlpha == 255) STATUS_BAR_HEIGHT_TRANSLUCENCY else colorAlpha / 255.0f
val opaqueColor = Color.argb(
ceil(alpha * 255).toInt(),
Color.red(color),
Color.green(color),
Color.blue(color)
)
setStatusBarColor(window, opaqueColor)
}
/**
* Sets the status bar background color.
* Uses the view-based background when available (edge-to-edge),
* falls back to the deprecated window API on older configurations.
*/
fun setStatusBarColor(window: Window?, color: Int) {
statusBarBackgroundView?.setBackgroundColor(color) ?: run {
@Suppress("DEPRECATION")
window?.statusBarColor = color
}
}
/**
* Gets the current status bar background color.
* Reads from the view-based background when available,
* falls back to the deprecated window API on older configurations.
*/
@JvmStatic
fun getStatusBarColor(window: Window?): Int? {
statusBarBackgroundView?.let { view ->
(view.background as? ColorDrawable)?.let { return it.color }
}
@Suppress("DEPRECATION")
return window?.statusBarColor
}
@JvmStatic
fun hideStatusBar(window: Window?, view: View) {
window?.let {
WindowInsetsControllerCompat(window, view).let { controller ->
controller.hide(WindowInsetsCompat.Type.statusBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}
@JvmStatic
fun showStatusBar(window: Window?, view: View) {
window?.let {
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.statusBars())
}
}
// endregion
// region Navigation Bar
@JvmStatic
fun hideNavigationBar(window: Window?, view: View) {
window?.let {
WindowInsetsControllerCompat(window, view).let { controller ->
controller.hide(WindowInsetsCompat.Type.navigationBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}
@JvmStatic
fun showNavigationBar(window: Window?, view: View) {
window?.let {
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.navigationBars())
}
}
/**
* Sets the navigation bar background color and icon appearance.
* Uses the view-based background when available (edge-to-edge),
* falls back to the deprecated window API on older configurations.
*/
@JvmStatic
fun setNavigationBarBackgroundColor(window: Window?, color: Int, lightColor: Boolean) {
window?.let {
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars = lightColor
}
if (isEdgeToEdgeActive) {
navBarBackgroundView?.setBackgroundColor(color)
} else {
@Suppress("DEPRECATION")
window?.navigationBarColor = color
}
}
// endregion
}