Last active
April 17, 2025 10:10
-
-
Save oguzhanaslann/4becf9c6f2fc914f72d1ab2a0c4eb59b to your computer and use it in GitHub Desktop.
WindowInsetsControllerCompat wrapper class to simplify the interface of it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Data class to hold margin states | |
| */ | |
| data class MarginState( | |
| val left: Int, | |
| val top: Int, | |
| val right: Int, | |
| val bottom: Int | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Data class to hold padding states | |
| */ | |
| data class PaddingState( | |
| val left: Int, | |
| val top: Int, | |
| val right: Int, | |
| val bottom: Int | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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