Getting started
Add Kapow to a Compose Multiplatform project and fire a first effect.
Install
Add the library to commonMain:
// shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.jantursky:kapow:1.3.0")
}
}
}
Or through a version catalog:
# gradle/libs.versions.toml
[versions]
kapow = "1.3.0"
[libraries]
kapow = { module = "io.github.jantursky:kapow", version.ref = "kapow" }
The core artifact depends on Compose runtime, foundation, ui and animation only. The hinge and feedback integrations are separate artifacts.
Your first change effect
Modifier.changeEffect takes the value to watch and the effect to play. When the value changes to something not equal to the old value, the effect plays once.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.changeEffect
@Composable
fun Counter() {
var count by remember { mutableIntStateOf(0) }
BasicText(
text = "Count: $count",
modifier = Modifier
.clickable { count++ }
.changeEffect(count, ChangeEffect.shine()),
)
}
The first composition is silent. Read Change effects for equality, isEnabled, cooldown and .delay().
Let particles leave their parents
Particle effects such as spray and rise draw around the element that fires them. If a parent clips its children, as a list row or a card does, the particles are cut off. Wrap a screen in KapowParticleLayer and the layer draws them instead, on top of everything inside it.
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.KapowParticleLayer
import com.jantursky.kapow.changeEffect
@Composable
fun Feed(likes: List<Int>) {
KapowParticleLayer(Modifier.fillMaxSize()) {
LazyColumn {
items(likes) { count ->
BasicText(
text = "Likes: $count",
modifier = Modifier.clipToBounds().changeEffect(count, ChangeEffect.spray()),
)
}
}
}
}
See Particle layer for what the layer does and does not do.
An effect that stays on
Modifier.conditionalEffect keeps an effect running while a Boolean is true.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.ConditionalEffect
import com.jantursky.kapow.conditionalEffect
import kotlin.time.Duration.Companion.seconds
@Composable
fun UnreadBadge(hasUnread: Boolean) {
Box(
Modifier
.size(12.dp)
.background(Color.Red, CircleShape)
.conditionalEffect(
condition = hasUnread,
effect = ConditionalEffect.repeat(ChangeEffect.pulse(shape = CircleShape), every = 2.seconds),
),
)
}
More in Conditional effects.
A transition
Kapow transitions run inside AnimatedVisibility or AnimatedContent. Compose’s EnterTransition cannot be extended, so you set the stock transitions to None and add Kapow’s with Modifier.kapowTransition. KapowAnimatedVisibility does both in one call.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.jantursky.kapow.KapowAnimatedVisibility
import com.jantursky.kapow.KapowEnter
import com.jantursky.kapow.KapowExit
@Composable
fun Panel(visible: Boolean) {
KapowAnimatedVisibility(
visible = visible,
enter = KapowEnter.iris(),
exit = KapowExit.poof(),
) {
Box(Modifier.size(160.dp).background(Color.DarkGray))
}
}
The Transitions guide explains why this shape is needed and how to combine Kapow with fadeIn().
Reduced motion
Kapow reads the platform setting and plays a quieter version of each effect when motion is reduced. This needs no setup. Reduced motion lists what each effect does instead, and how to override the setting, which matters on JVM desktop.
Run the demo
The live demo has a page per effect with controls and the source. It is a wasm app in samples/web in the repository.