Skip to content

Instantly share code, notes, and snippets.

@oguzhanaslann
Last active April 17, 2025 10:10
Show Gist options
  • Select an option

  • Save oguzhanaslann/4becf9c6f2fc914f72d1ab2a0c4eb59b to your computer and use it in GitHub Desktop.

Select an option

Save oguzhanaslann/4becf9c6f2fc914f72d1ab2a0c4eb59b to your computer and use it in GitHub Desktop.
WindowInsetsControllerCompat wrapper class to simplify the interface of it
/**
* Data class to hold margin states
*/
data class MarginState(
val left: Int,
val top: Int,
val right: Int,
val bottom: Int
)
/**
* Data class to hold padding states
*/
data class PaddingState(
val left: Int,
val top: Int,
val right: Int,
val bottom: Int
)
/**
* Data class to configure which dimensions should have insets applied
*/
class WindowInsetDimensions private constructor(
val left: Boolean = false,
val top: Boolean = false,
val right: Boolean = false,
val bottom: Boolean = false
) {
fun isNone() = !(left || top || right || bottom) // if all are false, return true
operator fun plus(other: WindowInsetDimensions): WindowInsetDimensions {
return WindowInsetDimensions(
left = this.left || other.left,
top = this.top || other.top,
right = this.right || other.right,
bottom = this.bottom || other.bottom
)
}
companion object {
val none = WindowInsetDimensions()
val horizontal = WindowInsetDimensions(left = true, right = true)
val vertical = WindowInsetDimensions(top = true, bottom = true)
val top = WindowInsetDimensions(top = true)
val bottom = WindowInsetDimensions(bottom = true)
val left = WindowInsetDimensions(left = true)
val right = WindowInsetDimensions(right = true)
}
}
import android.graphics.Color
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
class WindowManagerUtil private constructor(private val activity: ComponentActivity) {
val windowInsetsControllerCompat: WindowInsetsControllerCompat =
WindowInsetsControllerCompat(activity.window, activity.window.decorView)
var behavior: Behavior = Behavior.default
set(value) {
field = value
windowInsetsControllerCompat.systemBarsBehavior = value.value
}
private var windowsInsets: WindowInsets? = null
val DefaultLightScrim = Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
val DefaultDarkScrim = Color.argb(0x80, 0x1b, 0x1b, 0x1b)
init {
windowInsetsControllerCompat.systemBarsBehavior = behavior.value
activity.window.decorView.setOnApplyWindowInsetsListener { view, windowInsets ->
windowsInsets = windowInsets
view.onApplyWindowInsets(windowInsets)
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun isStatusBarVisible(): Boolean {
return windowsInsets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: false
}
@RequiresApi(Build.VERSION_CODES.R)
fun isNavigationBarVisible(): Boolean {
return windowsInsets?.isVisible(WindowInsetsCompat.Type.navigationBars()) ?: false
}
@RequiresApi(Build.VERSION_CODES.R)
fun isVisibleKeyboard(): Boolean {
return windowsInsets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
}
@RequiresApi(Build.VERSION_CODES.R)
fun isCaptionBarVisible(): Boolean {
return windowsInsets?.isVisible(WindowInsetsCompat.Type.captionBar()) ?: false
}
@RequiresApi(Build.VERSION_CODES.R)
fun isSystemBarsVisible(): Boolean {
return windowsInsets?.isVisible(WindowInsetsCompat.Type.systemBars()) ?: false
}
fun hideStatusBar() {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.statusBars())
}
fun showStatusBar() {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.statusBars())
}
fun setStatusBarVisibility(isVisible: Boolean) {
if (isVisible) {
showStatusBar()
} else {
hideStatusBar()
}
}
fun setStatusBarColor(color: Int) {
activity.window.statusBarColor = color
}
fun setNavigationBarColor(color: Int) {
activity.window.navigationBarColor = color
}
fun hideNavigationBar() {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars())
}
fun showNavigationBar() {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars())
}
fun setNavigationBarVisibility(isVisible: Boolean) {
if (isVisible) {
showNavigationBar()
} else {
hideNavigationBar()
}
}
fun hideKeyboard() {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.ime())
}
fun showKeyboard() {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.ime())
}
fun setKeyboardVisibility(isVisible: Boolean) {
if (isVisible) {
showKeyboard()
} else {
hideKeyboard()
}
}
fun hideCaptionBar() {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.captionBar())
}
fun showCaptionBar() {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.captionBar())
}
fun setCaptionBarVisibility(isVisible: Boolean) {
if (isVisible) {
showCaptionBar()
} else {
hideCaptionBar()
}
}
fun hideSystemBars() {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars())
}
fun showSystemBars() {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.systemBars())
}
fun setSystemBarsVisibility(isVisible: Boolean) {
if (isVisible) {
showSystemBars()
} else {
hideSystemBars()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun toggleStatusBar() {
if (isStatusBarVisible()) {
hideStatusBar()
} else {
showStatusBar()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun toggleNavigationBar() {
if (isNavigationBarVisible()) {
hideNavigationBar()
} else {
showNavigationBar()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun toggleKeyboard() {
if (isVisibleKeyboard()) {
hideKeyboard()
} else {
showKeyboard()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun toggleCaptionBar() {
if (isCaptionBarVisible()) {
hideCaptionBar()
} else {
showCaptionBar()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun toggleSystemBars() {
if (isSystemBarsVisible()) {
hideSystemBars()
} else {
showSystemBars()
}
}
fun enableEdgeToEdge(
statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(
DefaultLightScrim,
DefaultDarkScrim
)
) {
activity.enableEdgeToEdge(statusBarStyle, navigationBarStyle)
}
fun disableEdgeToEdge() {
WindowCompat.setDecorFitsSystemWindows(activity.window, true)
}
fun enterFullScreen() {
hideSystemBars()
enableEdgeToEdge()
}
fun exitFullScreen() {
showSystemBars()
disableEdgeToEdge()
}
fun setNavigationBarContrastEnforced(isEnforced: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
activity.window.isNavigationBarContrastEnforced = isEnforced
}
}
fun View.addSystemWindowInsets(
margin: WindowInsetDimensions = WindowInsetDimensions.none,
padding: WindowInsetDimensions = WindowInsetDimensions.none,
consume: Boolean = false,
@WindowInsetsCompat.Type.InsetsType insetsType: Int = WindowInsetsCompat.Type.systemBars()
) {
val initialMargins = (layoutParams as? ViewGroup.MarginLayoutParams)?.let {
MarginState(it.leftMargin, it.topMargin, it.rightMargin, it.bottomMargin)
}
val initialPadding = PaddingState(paddingLeft, paddingTop, paddingRight, paddingBottom)
/**
Prefer this instead of "android:fitsSystemWindows".
Refer to: https://medium.com/androiddevelopers/insets-handling-tips-for-android-15s-edge-to-edge-enforcement-872774e8839b
*/
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val systemInsets = insets.getInsets(insetsType)
view.applyMarginsIfNeeded(margin, initialMargins, systemInsets)
view.applyPaddingIfNeeded(padding, initialPadding, systemInsets)
if (consume) WindowInsetsCompat.CONSUMED else insets
}
}
private fun View.applyPaddingIfNeeded(
padding: WindowInsetDimensions,
initialPadding: PaddingState,
systemInsets: Insets
) {
if (!padding.isNone()) {
this.setPadding(
initialPadding.left + (if (padding.left) systemInsets.left else 0),
initialPadding.top + (if (padding.top) systemInsets.top else 0),
initialPadding.right + (if (padding.right) systemInsets.right else 0),
initialPadding.bottom + (if (padding.bottom) systemInsets.bottom else 0)
)
}
}
private fun View.applyMarginsIfNeeded(
margin: WindowInsetDimensions,
initialMargins: MarginState?,
systemInsets: Insets
) {
if (!margin.isNone() && initialMargins != null) {
this.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = initialMargins.left + (if (margin.left) systemInsets.left else 0)
topMargin = initialMargins.top + (if (margin.top) systemInsets.top else 0)
rightMargin = initialMargins.right + (if (margin.right) systemInsets.right else 0)
bottomMargin = initialMargins.bottom + (if (margin.bottom) systemInsets.bottom else 0)
}
}
}
@JvmInline
value class Behavior private constructor(val value: Int) {
companion object {
val default = Behavior(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT)
val immersive =
Behavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
}
}
companion object {
fun get(activity: ComponentActivity): WindowManagerUtil {
return WindowManagerUtil(activity)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment