Search Docs
Loading...
Skip to content

Composition Groups

Give the children of a group their own clock, run them faster with the group speed, and start them with the group.

6 mins
estimated time
GitHub

A composition group runs its children on its own clock, like a precomposition in a motion design tool. The clock starts when the group starts on the page, and the group’s speed runs it faster or slower. The editor has no composition control, so you set it with the block API.

This guide covers how to turn composition on, how to run the children faster, how to start them with the group, what a plain group does, and the error to expect. It ends with a frame export.

Creating the Lanes#

Each lane of the example is a group with a gray line and a ball on page, the 6-second page of the scene. The ball has a position/x track and crosses the page in 4 seconds of its own time. group groups the two blocks.

// Creates a lane: a group with a line and a ball that crosses the page in
// 4 seconds of its own time
fun createLane(
y: Float,
color: RGBAColor,
): DesignBlock {
val line = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(block = line, shape = engine.block.createShape(ShapeType.Rect))
engine.block.setFill(block = line, fill = engine.block.createFill(FillType.Color))
engine.block.setFillSolidColor(block = line, color = Color.fromRGBA(r = 0.85F, g = 0.85F, b = 0.85F, a = 1F))
engine.block.setWidth(block = line, value = 1680F)
engine.block.setHeight(block = line, value = 6F)
engine.block.setPositionX(block = line, value = 120F)
engine.block.setPositionY(block = line, value = y + 37F)
engine.block.appendChild(parent = page, child = line)
// Long enough for every lane clock, so the line stays visible
engine.block.setDuration(block = line, duration = 12.0)
val ball = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(block = ball, shape = engine.block.createShape(ShapeType.Ellipse))
engine.block.setFill(block = ball, fill = engine.block.createFill(FillType.Color))
engine.block.setFillSolidColor(block = ball, color = color)
engine.block.setWidth(block = ball, value = 80F)
engine.block.setHeight(block = ball, value = 80F)
engine.block.setPositionY(block = ball, value = y)
engine.block.appendChild(parent = page, child = ball)
engine.block.setDuration(block = ball, duration = 4.0)
engine.block.setKeyframes(
block = ball,
property = "position/x",
keyframes = listOf(
Keyframe(time = 0F, value = KeyframeValue.Float(120F)),
Keyframe(time = 1F, value = KeyframeValue.Float(1720F)),
),
)
val lane = engine.block.group(blocks = listOf(line, ball))
engine.block.setDuration(block = lane, duration = 6.0)
return lane
}

Turning Composition On#

group/composition is a bool property of a group and is off by default. setBoolean turns it on, and getBoolean reads it. The children of the first lane now run on the group’s clock, which starts when the group starts on the page.

// The first lane is a composition group. Its children run on the group's
// own clock, which starts when the group starts on the page.
val normal = createLane(y = 150F, color = Color.fromRGBA(r = 0.6F, g = 0.6F, b = 0.6F, a = 1F))
engine.block.setBoolean(block = normal, property = "group/composition", value = true)
// true
val normalIsComposition = engine.block.getBoolean(block = normal, property = "group/composition")

Running the Children Faster#

setPlaybackSpeed on a composition group runs its children that many times faster. The speed of a group has no upper bound, and getPlaybackSpeed reads it back. At speed 2, the blue ball crosses the page in 2 seconds. Its duration of 4 seconds is measured on the composition clock, so the ball disappears after 2 seconds of the page.

// At speed 2, the children see 2 seconds for every second of the page
val fast = createLane(y = 400F, color = Color.fromRGBA(r = 0.2F, g = 0.4F, b = 0.9F, a = 1F))
engine.block.setBoolean(block = fast, property = "group/composition", value = true)
engine.block.setPlaybackSpeed(block = fast, speed = 2F)
// 2.0
val fastSpeed = engine.block.getPlaybackSpeed(block = fast)

Starting the Children With the Group#

The time offsets of the children are measured on the composition clock, which starts with the group. setTimeOffset starts the third group 1 second into the page, so its ball starts 1 second later than the others.

// The group starts 1 second into the page, and its children start with it
val late = createLane(y = 650F, color = Color.fromRGBA(r = 0.9F, g = 0.3F, b = 0.4F, a = 1F))
engine.block.setBoolean(block = late, property = "group/composition", value = true)
engine.block.setTimeOffset(block = late, offset = 1.0)
engine.block.setDuration(block = late, duration = 5.0)

A Plain Group#

A group without composition keeps its children on the page clock and ignores its speed. The green ball crosses the page at the same pace as the gray one, although its group has speed 2.

// Without composition, the speed of a group has no effect
val plain = createLane(y = 900F, color = Color.fromRGBA(r = 0.2F, g = 0.7F, b = 0.3F, a = 1F))
engine.block.setPlaybackSpeed(block = plain, speed = 2F)
// false
val plainIsComposition = engine.block.getBoolean(block = plain, property = "group/composition")
Log.i(TAG, "$normalIsComposition, $fastSpeed, $plainIsComposition")

Handling Errors#

A group runs at 0.25 times the page speed or faster. A lower speed throws an EngineException with a code, and the speed stays unchanged.

val errorCode = try {
// A group runs at 0.25 times the page speed or faster
engine.block.setPlaybackSpeed(block = normal, speed = 0.1F)
null
} catch (error: EngineException) {
// EDITOR.PLAYBACK_SPEED_TOO_LOW
error.code
}

Exporting a Frame#

We export the page as PNG at 1.5 seconds. The gray and green balls are at 37.5 percent of their lanes, the blue ball at 75 percent, and the red ball at 12.5 percent, because its group started 1 second later.

// Export the frame at 1.5 seconds
engine.block.setPlaybackTime(block = page, time = 1.5)
val previewPng = engine.block.export(block = page, mimeType = MimeType.PNG)
Log.i(TAG, "PNG bytes: ${previewPng.remaining()}")

Composition Groups and Other Block Operations#

  • Nested composition groups multiply their speeds.
  • A child’s duration is not clamped to the group duration. A child is hidden after its end on the composition clock.
  • playback/timeRemap with playback/timeRemapEnabled replaces the group’s local time with a value or a keyframe track, and the speed still multiplies it.
  • Ungrouping a composition group converts the time offset and duration of each child to the page clock, so the children keep playing at the same moments.
  • Saving and loading keep group/composition and the speed, and undo and redo restore them.
  • The HTML5 export does not export the composition clock, so the children play at page time there.

Troubleshooting#

  • The speed is too low (EDITOR.PLAYBACK_SPEED_TOO_LOW): use a speed of 0.25 or more.
  • The speed is not a number (BLOCK.VALUE_NOT_FINITE): pass a finite speed.
  • The speed has no effect: turn on group/composition for the group.
  • A child disappears early: its duration is measured on the composition clock, so a faster group ends it sooner on the page. Make the child longer.

API Reference#

Method Purpose
engine.block.create(blockType=_) Create a graphic block
engine.block.createShape(type=_) Create a rect or ellipse shape
engine.block.setShape(block=_, shape=_) Give a graphic its shape
engine.block.createFill(fillType=_) Create a color fill
engine.block.setFill(block=_, fill=_) Give a graphic its fill
engine.block.setFillSolidColor(block=_, color=_) Set the fill color
engine.block.setWidth(block=_, value=_) Set the width of a block
engine.block.setHeight(block=_, value=_) Set the height of a block
engine.block.setPositionX(block=_, value=_) Set the x position of a block
engine.block.setPositionY(block=_, value=_) Set the y position of a block
engine.block.appendChild(parent=_, child=_) Add a block to the page
engine.block.setDuration(block=_, duration=_) Set the duration of a block or group
engine.block.setKeyframes(block=_, property=_, keyframes=_) Move the ball
engine.block.group(blocks=_) Group blocks into a group
engine.block.setBoolean(block=_, property="group/composition", value=_) Turn composition on or off
engine.block.getBoolean(block=_, property="group/composition") Check whether a group is a composition group
engine.block.setPlaybackSpeed(block=_, speed=_) Set the speed of a group, 0.25 or more
engine.block.getPlaybackSpeed(block=_) Read the speed of a group
engine.block.setTimeOffset(block=_, offset=_) Start a group later on the page
engine.block.setPlaybackTime(block=_, time=_) Seek before you export a frame
engine.block.export(block=_, mimeType=_) Export the frame as PNG

Next Steps#