Skip to content

Drop-in animated splash screens for Android. Uses Jetpack Compose + AndroidX SplashScreen for smooth brand experiences without platform fights.

License

Notifications You must be signed in to change notification settings

kibotu/androidx-splashscreen-compose

Repository files navigation

androidx-splashscreen-compose 🎨

Maven Central Version Android CI API API API Gradle Version Kotlin License

Let's cut to the chase: Android's default splash screen is boring. This library lets you create stunning animated splash screens using Compose without the headache. No more static drawables, no more janky transitions.

screenshot

What's the Point? 🎯

Look, we all know the pain points:

  • Android's splash screen is just a static image
  • Animations? Good luck with that
  • Transitions that look like they're from 2010
  • Zero Compose support out of the box

Here's what you get with androidx-splashscreen-compose:

  • Drop-in Compose animations that actually look good
  • Smooth transitions that don't make users cringe
  • Complete control over timing and animations
  • Works with AndroidX SplashScreen, not against it

Get Started in 30 Seconds πŸš€

  1. Add the dependency:
implementation 'net.kibotu:androidx-splashscreen-compose:{latest-version}'
  1. Create your splash screen:
class MainActivity : ComponentActivity() {
    
    private var splashScreen: SplashScreenDecorator? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // Initialize before super.onCreate()
        splashScreen = splash {
            content {
                HeartBeatAnimation(
                    isVisible = isVisible.value,
                    exitAnimationDuration = exitAnimationDuration.milliseconds,
                    onStartExitAnimation = { startExitAnimation() }
                )
            }
        }
        
        // start your own splash screen animation
        splashScreen?.shouldKeepOnScreen = false
        
        super.onCreate(savedInstanceState)
        
        setContent {
            MyAppTheme {
                MainScreen()
            }
        }
    }

    override fun onStart() {
        super.onStart()
        
        // trigger your custom splash animation
        splashScreen?.dismiss()
    }
}

That's it. No, really.

Show Me the Good Stuff 🎨

Heartbeat Animation Example

Here's a real-world example of a heartbeat animation that actually ships in production apps:

fun HeartBeatAnimation(
    modifier: Modifier = Modifier,
    isVisible: Boolean = true,
    exitAnimationDuration: Duration = Duration.ZERO,
    onStartExitAnimation: () -> Unit = {}
) {
    // Animation constants
    val rippleCount = 4
    val rippleDurationMs = 3313
    val rippleDelayMs = rippleDurationMs / 8
    val baseSize = 144.dp
    val containerSize = 288.dp

    // Track exit animation state
    var isExitAnimationStarted by remember { mutableStateOf(false) }

    // Trigger exit animation when visibility changes
    LaunchedEffect(isVisible) {
        if (!isVisible && !isExitAnimationStarted) {
            isExitAnimationStarted = true
            onStartExitAnimation()
        }
    }

    // Calculate screen diagonal for exit animation scaling
    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp
    val screenHeight = configuration.screenHeightDp
    val screenDiagonal = sqrt((screenWidth * screenWidth + screenHeight * screenHeight).toFloat())

    // Exit animation scale with snappy easing
    val snappyEasing = CubicBezierEasing(0.2f, 0.0f, 0.2f, 1.0f)
    val exitAnimationScale by animateFloatAsState(
        targetValue = if (isExitAnimationStarted) screenDiagonal / baseSize.value else 0f,
        animationSpec = tween(
            durationMillis = exitAnimationDuration.toInt(DurationUnit.MILLISECONDS),
            easing = snappyEasing
        ),
        label = "exitScale"
    )

    // Infinite ripple animation transition
    val infiniteTransition = rememberInfiniteTransition(label = "heartbeatTransition")

    Box(
        modifier = modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        // Only show ripples when visible and not exiting
        if (isVisible && !isExitAnimationStarted) {
            Box(
                modifier = Modifier.size(containerSize),
                contentAlignment = Alignment.Center
            ) {
                // Create ripple circles with staggered animations
                repeat(rippleCount) { index ->
                    RippleCircle(
                        infiniteTransition = infiniteTransition,
                        index = index,
                        rippleDurationMs = rippleDurationMs,
                        rippleDelayMs = rippleDelayMs,
                        baseSize = baseSize
                    )
                }
            }
        }

        // Exit animation circle
        if (isExitAnimationStarted) {
            Box(
                modifier = Modifier
                    .size(baseSize)
                    .graphicsLayer {
                        scaleX = exitAnimationScale
                        scaleY = exitAnimationScale
                    }
                    .background(
                        color = blueCatalina,
                        shape = CircleShape
                    )
            )
        }
    }
}

@Composable
private fun RippleCircle(
    infiniteTransition: InfiniteTransition,
    index: Int,
    rippleDurationMs: Int,
    rippleDelayMs: Int,
    baseSize: Dp
) {
    val totalDuration = rippleDurationMs + (rippleDelayMs * index)
    val easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)

    // Animate scale from 1f to 4f
    val animatedScale by infiniteTransition.animateFloat(
        initialValue = 1f,
        targetValue = 4f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = totalDuration,
                delayMillis = rippleDelayMs * index,
                easing = easing
            ),
            repeatMode = RepeatMode.Restart
        ),
        label = "rippleScale$index"
    )

    // Animate alpha from 0.25f to 0f
    val animatedAlpha by infiniteTransition.animateFloat(
        initialValue = 0.25f,
        targetValue = 0f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = totalDuration,
                delayMillis = rippleDelayMs * index,
                easing = easing
            ),
            repeatMode = RepeatMode.Restart
        ),
        label = "rippleAlpha$index"
    )

    Box(
        modifier = Modifier
            .size(baseSize)
            .graphicsLayer {
                scaleX = animatedScale
                scaleY = animatedScale
                alpha = animatedAlpha
            }
            .background(
                color = blueCatalina,
                shape = CircleShape
            )
    )
}

Pro Tips πŸ’‘

  1. Timing is Everything

    splashScreen = splash {
        content {
            exitAnimationDuration = 800L // Sweet spot for most animations
            composeViewFadeDurationOffset = 200L // Prevents jarring transitions
        }
    }
  2. Memory Management

    override fun onDestroy() {
        splashScreen = null // Don't leak memory
        super.onDestroy()
    }
  3. Performance First

    • Use rememberInfiniteTransition() for repeating animations
    • Keep animations under 1 second (users hate waiting)
    • Test on low-end devices

Comparison with Alternatives πŸ”„

vs only AndroidX SplashScreen

Feature androidx-splashscreen-compose AndroidX SplashScreen
Animation Support βœ… Full Compose animations ❌ Static vector only
Custom Content βœ… Any Composable ❌ Icon + background only
Transition Control βœ… Precise timing control ❌ Limited control
Branding Flexibility βœ… Complete creative freedom ❌ Very constrained
Implementation Complexity βœ… Simple DSL setup βœ… Minimal setup
Performance βœ… Optimized Compose rendering βœ… Lightweight
Backward Compatibility βœ… Built on AndroidX βœ… Native support

vs Custom Splash Activities

Feature androidx-splashscreen-compose Custom Splash Activity
Android 12+ Compliance βœ… Fully compliant ❌ Requires extra work
App Launch Performance βœ… No additional activity ❌ Extra activity overhead
Transition Seamlessness βœ… Native system integration ❌ Potential flicker
Code Complexity βœ… Single file setup ❌ Multiple components
Maintenance βœ… Library handles updates ❌ Manual Android compliance

When to Use What πŸ€”

Use androidx-splashscreen-compose when:

  • You need animations that don't look like they're from a 2010 tutorial
  • Your brand guidelines require more than a static logo
  • You want Compose-based animations without the setup headache
  • Android 12+ compliance with zero additional effort
  • Seamless integration with existing AndroidX SplashScreen setup

Stick with AndroidX SplashScreen when:

  • A static logo is all you need
  • You're optimizing for the smallest possible APK size
  • You don't need any custom animations

Choose Custom Splash Activity when:

  • Pre-Android 12 apps with no compliance requirements
  • Complex initialization flows requiring multiple screens
  • Non-Compose apps with View-based animations

Compatibility πŸ“±

  • Minimum Android SDK: 21
  • Target Android SDK: 36
  • Kotlin: 2.2.0
  • Java: 17
  • Gradle: 8.12.0

Contributing 🀝

Got ideas? Found a bug? PRs are welcome:

  1. Fork it
  2. Create your feature branch (git checkout -b feature/amazing)
  3. Commit your changes (git commit -m 'Add something amazing')
  4. Push to the branch (git push origin feature/amazing)
  5. Open a Pull Request

License πŸ“„

Apache 2.0 - do what you want, just don't blame us if something goes wrong. See LICENSE for the boring details.


About

Drop-in animated splash screens for Android. Uses Jetpack Compose + AndroidX SplashScreen for smooth brand experiences without platform fights.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

Languages