Triggers and interactions
Play an effect from a click or an event, follow press, hover and focus, and choose when the first composition plays.
changeEffect(value, effect) needs a value that changes. Three more ways to start an effect cover the cases where there is no such value.
Fire it yourself
A KapowTrigger plays the effect each time you call fire(), from a click handler, a view model event or anywhere else. Firing never recomposes anything, and a second fire() plays again even though nothing in state changed.
import androidx.compose.foundation.clickable
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.unit.dp
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.changeEffect
import com.jantursky.kapow.rememberKapowTrigger
@Composable
fun Party() {
val trigger = rememberKapowTrigger()
Box(
Modifier
.size(80.dp)
.changeEffect(trigger, ChangeEffect.confettiPop())
.clickable { trigger.fire() },
)
}
- One trigger can drive several elements. Each one plays its own effect.
- Fires that arrive within one frame play once. For spacing, put
.cooldown(…)on the effect. - A fire that happens before the element is composed plays nothing later.
isEnabled,feedback,.delay(…),.times(…)and the other wrappers work as for a value.
Follow press, hover and focus
Pass an InteractionSource and say which interaction counts: Pressed, Hovered, Focused or Dragged. A conditional effect stays on while the interaction holds. A change effect plays once each time it starts.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.KapowInteraction
import com.jantursky.kapow.changeEffect
import com.jantursky.kapow.conditionalEffect
@Composable
fun Card(onClick: () -> Unit) {
val source = remember { MutableInteractionSource() }
Box(
Modifier
.size(160.dp)
.conditionalEffect(source, KapowInteraction.Hovered, ConditionalEffect.glow(Color.Magenta))
.changeEffect(source, KapowInteraction.Pressed, ChangeEffect.pulse())
.hoverable(source)
.focusable(interactionSource = source)
.clickable(interactionSource = source, indication = null, onClick = onClick),
)
}
Interactions are read without recomposing. A source does not replay what happened before the element was composed, so an element that appears under a resting mouse starts idle and reacts to the next event. Hover needs a mouse, so it never happens on a phone, and focus needs a focusable element.
Rising and falling edges
edgeEffect plays when a Boolean turns true (KapowEdge.Rising) or false (KapowEdge.Falling), and ignores the other direction. It suits a success sparkle when isSaved turns true, or a shake when isValid turns false.
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.KapowEdge
import com.jantursky.kapow.edgeEffect
@Composable
fun SavedBadge(isSaved: Boolean) {
Box(Modifier.edgeEffect(isSaved, KapowEdge.Rising, ChangeEffect.sparkle()))
}
Play on the first composition
By default nothing plays when an element first appears. Set playOnStart = true on changeEffect, on a trigger or on edgeEffect for a welcome sparkle or a reveal. It plays once per element, not again when a lazy list reuses the item for other content, and an edgeEffect only plays it when the condition already matches the edge.
Remember your effects
Build an effect once. An effect created inside a composable is equal to the last one when its parameters are equal, so the usual ChangeEffect.spray() call is fine. One that captures a lambda, such as a label, is a new effect on each recomposition and restarts the running one: wrap it in remember.