Skip to content

Repository files navigation

Own Your Dialog

An Android Jetpack Compose project: a runnable set of samples showing what it takes to actually own a Compose Dialog: its scrim, its animation, and its back-gesture behavior, instead of accepting the platform's dim overlay and platform window animation.

Companion article: Own Your Dialog: Taking Control of Compose's Dialog Window

The problem

Compose's Dialog opens its own Android Window. That window comes with two things you didn't ask for and can't easily override from inside the composable: a platform dim scrim (a flat black overlay, not a themeable color) and a platform window enter/exit animation. Both happen outside Compose's control, so no Modifier, no animate*AsState, no Transition you write inside the dialog's content can touch them.

The fix is one function, called first thing inside the dialog's content:

@Composable
internal fun SetDialogWindow() {
    val view = LocalView.current
    val activityWindow = LocalActivity.current?.window
    val dialogWindow = (view.parent as? DialogWindowProvider)?.window
    dialogWindow?.setDimAmount(0f)
    dialogWindow?.setWindowAnimations(0)
    if (dialogWindow != null && activityWindow != null) {
        SideEffect {
            WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView).isAppearanceLightStatusBars =
                WindowInsetsControllerCompat(activityWindow, activityWindow.decorView).isAppearanceLightStatusBars
        }
    }
}

setDimAmount(0f) kills the platform scrim. setWindowAnimations(0) kills the platform window animation. The status-bar sync keeps the dialog's own window from flashing a different icon color than the host Activity's when it opens. Everything after that — scrim color, fade, scale, slide, how a back gesture reverses it — is Compose code you write and fully control.

Structure

dialog/     the reusable pieces: window neutralization, scrim, dialog shapes, predictive back
samples/    one screen per sample, each demonstrating one dimension of the technique

dialog/

  • OwnedDialog.ktSetDialogWindow(), OwnedDialogProperties, and OwnedDialog: a centered, scale-and-fade card. Two overloads — visible: Boolean for direct use inside a screen, and a raw scrimAlpha: () -> Float, cardScale: () -> Float version driven by whoever hosts it (a nav3 Scene, in this project).
  • DialogScrim.kt — the scrim every shape shares: fades with the animation, exposes a real accessibility action for tap-to-dismiss, and the verticalSlide layout helper that offsets a card by a fraction of its own measured height (no hardcoded pixel travel distances).
  • OwnedDialogScene.ktOwnedDialogSceneStrategy, a nav3 SceneStrategy that renders a back-stack entry as an OwnedDialog overlay instead of a normal destination.
  • OwnedDialogPredictiveBack.kt — bridges the dialog window's own predictive-back dispatcher (a Dialog opens a separate Window with its own OnBackInvokedDispatcher, so the host Activity's back handling never sees the gesture) and exposes onBackProgress/onBackCancelled/onBackCompleted so a caller can reverse its own animation live, mid-drag.
  • OwnedDialogWithPredictiveBack.kt, OwnedFloatingBottomDialog.kt, OwnedBottomSheetDialog.kt — the same window/scrim/back-gesture plumbing, reused across three different card shapes and positions.

samples/

Each sample is a screen reachable from the home screen, showing one specific combination.

# Sample What it shows
01 Rendered in place A screen holds a boolean and calls OwnedDialog(visible, ...) directly. Nav3 never sees it.
02 In place, with predictive back Same boolean-driven dialog, but a back gesture drag reverses the scrim fade and card scale live.
03 Rendered as a destination A NavKey on the nav3 back stack. OwnedDialogSceneStrategy renders it as a dialog overlay, with the same live predictive-back reversal.
04 Floating dialog, slide from bottom A different shape: anchored to the bottom with margin on every side, sliding in by its own height instead of scaling from the center. Predictive-back aware.
05 Simulated bottom sheet Edge to edge, flush with the bottom, rounded only on top — no ModalBottomSheet, just OwnedDialogProperties, a bottom-anchored Box, and verticalSlide. Predictive-back aware.
01 · Rendered in place
02 · Predictive back
03 · As a destination
04 · Floating, slide from bottom
05 · Simulated bottom sheet

Running it

./gradlew :app:installDebug

Or open the project in Android Studio and run the app configuration. Each sample screen has a button that shows its dialog; samples 02, 03, and 05 additionally react to a real system back gesture — drag from the edge and release early to cancel, the dialog snaps back open instead of dismissing.

License

Apache License 2.0.

About

Android Jetpack Compose samples for owning a Dialog's scrim, animation, and predictive back gesture instead of the platform defaults

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages