Skip to main content

Animation basics

Introduction#

In Litho we perform UI updates by changing state or props on the component tree instead of mutating the views directly. In a similar way, the animation framework adds transitions to components that are triggered when regenerating a tree. It is important to avoid accessing the underlying view to add animations as these values will probably not be preserved.

Transitions can be used to animate view properties when they change between layouts, i.e. due to a state update or new props from the parent. In the following example we will create a layout with a simple state change triggered by a click. We will then demonstraight the different ways of applying transitions animations.

When we make changes to a tree due to a new state, these changes happen immediately. Let's imagine that we have a simple Component that renders a yellow square, and aligns it to either the right or left edge of screen based on value of the @State boolean toRight.

@LayoutSpec
public class SimpleAllLayoutTransitionComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @State boolean toRight) {
return Column.create(c)
.clickHandler(SimpleAllLayoutTransitionComponent.onClickEvent(c))
.child(SolidColor.create(c).color(YELLOW).widthDip(80).heightDip(80))
.alignItems(toRight ? YogaAlign.FLEX_END : YogaAlign.FLEX_START)
.build();
}
@OnEvent(ClickEvent.class)
static void onClickEvent(ComponentContext c, @FromEvent View view) {
SimpleAllLayoutTransitionComponent.onUpdateState(c);
}
@OnUpdateState
static void onUpdateState(StateValue<Boolean> toRight) {
toRight.set(!toRight.get());
}

When the value of the state changes we re-render the ComponentTree which makes the square appear to “jump” from its previous position to the new one. In the next stem we will show how to replace this "jump" with a transition animation.

Bounds Transitions#

There is a simple way to add bounds animations to all transitioning components between tree changes. Add the following:

@OnCreateTransition
static Transition onCreateTransition(ComponentContext c) {
return Transition.allLayout();
}

Returning Transition.allLayout() will create a Transition that will automatically animate any changes to position or width and height.

This only works when changing the bounds of a component and will not work with:

  • Other properties including scale, alpha and rotation.
  • Components that are being added or removed.

Transitions#

Once we move out of just bounds and instead of animating the X we want to animate ALPHA there are just a couple of things you need to add to your code to make it happen.

For more control over the transitions we can use these apis:

  • @OnCreateTransition method. You need to add a method annotated with @OnCreateTransition to your Spec, which is what we use to define the transition animations. It should return a Transition, and its first argument should always be of ComponentContext type. As other lifecycle methods in a Spec, it could also have @Prop arguments, as well as arguments of StateValue type, although this comes at a cost - more on this later.
  • Transition is a description of which Component/Property (mandatory) and how (optional) you want to animate. You will not use a constructor to create Transition instances, instead you will use one of the provided Builders.
  • transitionKey is an identifier that you normally assign to a Component that you want to animate, and then use it when defining Transition.
  • AnimatedProperties are used to target the property of a Component that should be animated when its value changes.

To put it all together, here is what it would look like in our case:

@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());
}
}

Notice that we:

  1. On line 16 we assign a transitionKey to the SolidColor component using Component.Builder#transitionKey() method.
  2. On lines 22-23 we create a Transition using Transition.create() that takes a transitionKey and then specify the property of the component using .animate() method that takes an AnimatedProperty.

Both of these methods take a variable number of arguments, so multiple Transitions may be expressed like:

private static final String SQUARE_KEY = "square";
private static final String OVAL_KEY = "oval";
private static final String ANOTHER_SHAPE = "another_shape";
...
@OnCreateTransition
static Transition onCreateTransition(ComponentContext c) {
return Transition.create(SQUARE_KEY, OVAL_KEY, ANOTHER_SHAPE)
.animate(AnimatedProperties.X, AnimatedProperties.Y);
}

The transitions animations API supports three types of transitions, change, appear and disappear which work differently depending on how the tree changes between states. In this example we did a change transition.