Animating View Properties with Dynamic Props
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).
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.
DynamicValue
s should only be updated on the main thread.
Common Dynamic Propsβ
The dynamic properties that are available for all Component
s 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β
MountSpec
s 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.