Transitions
Transitions are an important concept in Litho, and the Kotlin API provides a set of powerful hooks for transition support. This makes it possible to replicate the behaviors of both @OnCreateTransition
and @OnUpdateStateWithTransition
in Kotlin.
useTransition APIsβ
useTransition
registers a transition (or set of transitions) to be applied when the current layout is committed. Two variants of this API are available for different use-cases.
When a transition is unconditional, and does not have any dependencies, such case can be represented by the simple and efficient useTransition API. This corresponds to the simplest use-case of the Spec API's @OnCreateTransition
.
- Kotlin API
- Java API
class AlphaTransitionKComponent : KComponent() {
override fun ComponentScope.render(): Component {
val isHalfAlpha = useState { false }
useTransition(Transition.create(SQUARE_KEY).animate(AnimatedProperties.ALPHA))
return Column(style = Style.onClick { isHalfAlpha.update { !it } }) {
child(
Row(
style =
Style.transitionKey(context, SQUARE_KEY, Transition.TransitionKeyType.GLOBAL)
.backgroundColor(Color.YELLOW)
.width(80.dp)
.height(80.dp)
.alpha(if (isHalfAlpha.value) 0.5f else 1.0f)))
}
}
}
@LayoutSpec
public class AlphaTransitionComponentSpec {
private static final String SQUARE_KEY = "square";
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @State boolean isHalfAlpha) {
return Column.create(c)
.clickHandler(AlphaTransitionComponent.onClickEvent(c))
.child(
Row.create(c)
.backgroundColor(YELLOW)
.widthDip(80)
.heightDip(80)
.alpha(isHalfAlpha ? 0.5f : 1.0f)
.transitionKey(SQUARE_KEY))
.build();
}
@OnCreateTransition
static Transition onCreateTransition(ComponentContext c) {
return Transition.create(SQUARE_KEY).animate(AnimatedProperties.ALPHA);
}
@OnEvent(ClickEvent.class)
static void onClickEvent(ComponentContext c, @FromEvent View view) {
AlphaTransitionComponent.onUpdateState(c);
}
@OnUpdateState
static void onUpdateState(StateValue<Boolean> isHalfAlpha) {
isHalfAlpha.set(!isHalfAlpha.get());
}
}
However, for more complex use-cases where the transition is directly anchored to a set of dependencies. These may be states, props, or derived values. In such case, the more powerful useTransition
with dependency API may be used. This API makes it possible to re-evaluate the transition whenever any of the dependencies change. It also provides access to the previous and next values of the declared dependency so that they may participate in the evaluation of the resulting transition. This ensures full parity with all variations of @OnCreateTransition
as well as the transition part of @OnUpdateStateWithTransition
- Kotlin API
val state = useState { TriState.HEIGHT }
useTransition(state.value) {
val (previous, next) = diffOf(state.value)
val animator =
when (if (previous == null || next == null) 0 else previous.ordinal + next.ordinal) {
1 -> Transition.SPRING_WITH_OVERSHOOT
2 -> Transition.timing(1_000, AccelerateDecelerateInterpolator())
3 -> Transition.springWithConfig(250.0, 10.0)
else -> Transition.timing(0)
}
Transition.create(Transition.TransitionKeyType.GLOBAL, "fancy-component")
.animate(*AnimatedProperties.AUTO_LAYOUT_PROPERTIES)
.animator(animator)
}
Which useTransition to use when?β
Scenario | simple useTransition | useTransition with dependencies |
---|---|---|
The same transition is always applied unconditionally | β | |
Transition only needs access to the current value of state, prop or derived value | β | |
Transition may change depending on some value | β | |
Transition is only applied whenever specific value changes | β | |
Resulting transition depends on the previous and/or next value of some dependency | β |
- Kotlin API
override fun ComponentScope.render(): Component {
val number = useState { 0 }
val delta = useBinding(0)
useTransition(number.value) {
val (previous, next) = diffOf(number.value)
val d = if (previous == null || next == null) next ?: 0 else next - previous
delta.set(d)
Transition.create(Transition.TransitionKeyType.GLOBAL, "bubble")
.animate(AnimatedProperties.SCALE)
.animator(Transition.springWithConfig(250.0, 10.0))
}
val text = buildString {
val d = delta.get()
if (d == 0) append("Tap me") else append(if (d > 0) "+" else "-").append(d.absoluteValue)
}
return Text(
text,
alignment = TextAlignment.CENTER,
verticalGravity = VerticalGravity.CENTER,
style =
Style.width(100.dp)
.height(100.dp)
.alignSelf(YogaAlign.CENTER)
.transitionKey(context, "bubble", Transition.TransitionKeyType.GLOBAL)
.margin(all = 10.dp)
.scale(lerp((number.value - MIN) / (MAX - MIN).toFloat(), 0.5f, 1.5f))
.background(RoundedRect(0xff6ab071, 8.dp))
.onClick { number.update(Random.nextInt(MIN, MAX)) })
}
Migrating from @OnCreateTransition/@OnUpdateStateWithTransitionβ
The table below shows a comparison of different scenarios implemented via Spec-Gen API and their equivalent Kotlin API
Scenariosβ
Simple transition without parametersβ
Spec-Gen API | Kotlin API |
---|---|
|
|
Transition needs only current value of state/prop/derived valueβ
Spec-Gen API | Kotlin API |
---|---|
|
|
Evaluate transition only if state has changedβ
Spec-Gen API | Kotlin API |
---|---|
|
|
Dependency change from specific value to anotherβ
Spec-Gen API | Kotlin API |
---|---|
|
|
Another complex use-caseβ
Spec-Gen API | Kotlin API |
---|---|
|
|
Interplay of state and transitionβ
Spec-Gen API | Kotlin API |
---|---|
|
|