Skip to main content

Animating View Properties with Dynamic Props

caution

This section contains information about the old Java Spec API. For new development, the Kotlin API is recommended (see the Animating View Properties with Dynamic Props page in the 'Animations' section).

note

Dynamic props are properties that are applied directly to a View or Drawable. They are updated without computing a layout or remounting. This makes them efficient for use in animations or other dynamic UIs. Dynamic props are initialised and updated using DynamicValue.

DynamicValue​

To control a dynamic prop, you should create a DynamicValue and assign it to that prop. You can then keep a reference to the DynamicValue and use it to directly set values (such as in a callback or an Animator). Use the set() function to set new values from the main thread.

caution

DynamicValues should only be updated on the main thread.

Common Dynamic Props​

The dynamic properties that are available for all Components are:

  • Alpha
  • Scale X/Y
  • Translation X/Y
  • Background Color
  • Foreground Color
  • Rotation
  • Elevation

To use these, create and pass a DynamicValue<T> object to the component.

For Spec components these props can be set in the builder:

MyComponent.create(c).alpha(dynamicAlpha).build()

The DynamicValue can be updated by calling its set() function.

The following code sample shows a Component that renders a square in the middle of the screen. The alpha and scale props have been set to the DynamicValue's, which are updated by two SeekBars.

@LayoutSpec
object CommonDynamicPropsComponentSpec {

@OnCreateInitialState
fun onCreateInitialState(
c: ComponentContext,
alpha: StateValue<DynamicValue<Float>>,
scale: StateValue<DynamicValue<Float>>,
) {
alpha.set(DynamicValue<Float>(1f))
scale.set(DynamicValue<Float>(1f))
}

@OnCreateLayout
fun onCreateLayout(
c: ComponentContext,
@State alpha: DynamicValue<Float>,
@State scale: DynamicValue<Float>,
): Component {
val square =
Column.create(c)
.widthDip(100f)
.heightDip(100f)
.backgroundRes(R.color.primaryColor)
.alignSelf(YogaAlign.CENTER)
.scaleX(scale)
.scaleY(scale)
.alpha(alpha)
.build()

return Column.create(c)
.justifyContent(YogaJustify.SPACE_BETWEEN)
.paddingDip(YogaEdge.ALL, 20f)
.child(
SeekBar.create(c).heightDip(14f).widthPercent(100f).initialValue(1f).onProgressChanged {
alpha.set(it)
})
.child(square)
.child(
SeekBar.create(c).heightDip(14f).widthPercent(100f).initialValue(1f).onProgressChanged {
scale.set(it)
})
.build()
}
}

The following short animation illustrates the component in action.

To see how other common dynamic props can be modified, see the All Common Dynamic Props example in the Sample app, which is illustrated in the following animation.

Custom Dynamic Props for MountSpecs​

MountSpecs can define custom dynamic props. Props annotated with @Prop(dynamic = true) can be set to a DynamicValue. Setting this value will trigger a custom @OnBindDynamicValue function in the component.

Consider a MountSpec that renders a clock face:

@MountSpec
object ClockFaceSpec {

@OnCreateMountContent
fun onCreateMountContent(androidContext: Context): SimpleClockView {
return SimpleClockView(androidContext)
}

@OnBindDynamicValue
fun onBindTime(simpleClockView: SimpleClockView, @Prop(dynamic = true) time: Long) {
simpleClockView.time = time
}
}

Here, the MountSpec delegates the drawing of a clock face to SimpleClockView. Marking the time prop with @Prop(dynamic = true) will cause an additional method to be generated in the builder that accepts a DynamicValue. The method to set a static value will also remain available. When set() is called on the DynamicValue, the @OnBindDynamicValue function will be executed. In this example, the new time is passed to the ClockView to draw the updated clock face.

Next, consider a slider to set the time on the clock face.

@LayoutSpec
object CustomDynamicPropsComponentSpec {

@OnCreateInitialState
fun onCreateInitialState(c: ComponentContext, time: StateValue<DynamicValue<Long>>) {
time.set(DynamicValue<Long>(0))
}

@OnCreateLayout
fun onCreateLayout(c: ComponentContext, @State time: DynamicValue<Long>): Component =
Column.create(c)
.alignItems(YogaAlign.CENTER)
.paddingDip(YogaEdge.ALL, 20f)
.child(
SeekBar.create(c)
.heightDip(14f)
.widthPercent(100f)
.initialValue(1f)
.onProgressChanged { time.set((it * TimeUnit.HOURS.toMillis(12)).toLong()) })
.child(
ClockFace.create(c)
.time(time)
.widthDip(200f)
.heightDip(200f)
.marginDip(YogaEdge.TOP, 20f)
.build())
.build()
}

This is illustrated in the following short animation.

Animating Dynamic Props​

Dynamic Props values can be used with Android Animators to create custom animations. The following example uses a ValueAnimator to animate the dynamic value time, defined in the previous value.

@LayoutSpec
object AnimateDynamicPropsComponentSpec {

@OnCreateInitialState
fun onCreateInitialState(
c: ComponentContext,
time: StateValue<DynamicValue<Long>>,
animator: StateValue<AtomicReference<ValueAnimator?>>,
) {
time.set(DynamicValue<Long>(0))
animator.set(AtomicReference<ValueAnimator?>(null))
}

@OnCreateLayout
fun onCreateLayout(
c: ComponentContext,
@State time: DynamicValue<Long>,
@State animator: AtomicReference<ValueAnimator?>,
): Component? {
val startAnimator: (ClickEvent) -> Unit = {
animator.get()?.let { it.cancel() }
animator.set(
ValueAnimator.ofInt(0, TimeUnit.HOURS.toMillis(12).toInt()).apply {
duration = 2000
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { time.set((it.animatedValue as Int).toLong()) }
})
animator.get()?.start()
}

return Column.create(c)
.paddingDip(YogaEdge.ALL, 20f)
.alignItems(YogaAlign.CENTER)
.child(
Text.create(c)
.text("Click to Start Animation")
.clickHandler(eventHandler(startAnimator)))
.child(
ClockFace.create(c)
.time(time)
.widthDip(200f)
.heightDip(200f)
.marginDip(YogaEdge.TOP, 20f)
.build())
.build()
}
}

A DynamicValue is used to represent time. This is passed to the Component as a prop and kept as a reference to it so it can be updated. In a click event, a ValueAnimator is set up that updates the time DynamicValue each frame (see the following animation). The ValueAnimator is stored in a reference so that it can be cancelled if necessary.

For more examples of creating Animations using Common Dynamic Props, see the Animations Cook Book in the Sample App.