androidx.constraintlayout.compose

Interfaces

BaselineAnchorable

Represents the FirstBaseline of a layout that can be anchored using linkTo in their Modifier.constrainAs blocks.

ConstraintLayoutTagParentData
ConstraintSet

Immutable description of the constraints used to layout the children of a ConstraintLayout.

DesignInfoProvider

Interface used for Studio tooling.

Dimension

Represents a dimension that can be assigned to the width or height of a childConstrainedLayoutReference.

Dimension.Coercible

A Dimension that can be assigned both min and max bounds.

Dimension.MaxCoercible

A Dimension that can be assigned a max bound.

Dimension.MinCoercible

A Dimension that can be assigned a min bound.

HorizontalAnchorable

Represents a horizontal side of a layout (i.e top and bottom) that can be anchored using linkTo in their Modifier.constrainAs blocks.

InvalidationStrategy.OnIncomingConstraints

Functional interface to implement invalidation on incoming constraints.

LayoutInformationReceiver
MotionCarouselScope
MotionItemsProvider
MotionScene

Information for MotionLayout to animate between multiple ConstraintSets.

Transition

Defines interpolation parameters between two ConstraintSets.

VerticalAnchorable

Represents a vertical side of a layout (i.e start and end) that can be anchored using linkTo in their Modifier.constrainAs blocks.

Classes

Arc

Determines a specific arc direction of the widget's path on a Transition.

BaseKeyFrameScope

The base/common scope for individual KeyFrame declarations.

BaseKeyFramesScope

The base/common scope for KeyFrames.

ChainStyle

The style of a horizontal or vertical chain.

ConstrainScope

Scope that can be used to constrain a layout.

ConstrainedLayoutReference

Represents a layout within a ConstraintLayout.

ConstraintLayoutBaseScope

Common scope for ConstraintLayoutScope and ConstraintSetScope, the content being shared between the inline DSL API and the ConstraintSet-based API.

ConstraintLayoutBaseScope.BaselineAnchor

Represents a horizontal anchor corresponding to the FirstBaseline of a layout that other layouts can link to in their Modifier.constrainAs or constrain blocks.

ConstraintLayoutBaseScope.HorizontalAnchor

Represents a horizontal anchor (e.g. top/bottom of a layout, guideline) that layouts can link to in their Modifier.constrainAs or constrain blocks.

ConstraintLayoutBaseScope.VerticalAnchor

Represents a vertical anchor (e.g. start/end of a layout, guideline) that layouts can link to in their Modifier.constrainAs or constrain blocks.

ConstraintLayoutScope

Scope used by the inline DSL of ConstraintLayout.

ConstraintLayoutScope.ConstrainedLayoutReferences

Convenience API for creating multiple ConstrainedLayoutReference via createRefs.

ConstraintSetRef
ConstraintSetScope

Scope used by the ConstraintSet DSL.

ConstraintSetScope.ConstrainedLayoutReferences
CurveFit

Type of fit applied between curves.

DebugFlags

Flags to use with MotionLayout to enable visual debugging.

Easing

Supported Easing curves.

FlowStyle

Defines how widgets are spaced in a chain

GridFlag

GridFlag defines the available flags of Grid SubGridByColRow: reverse the width and height specification for spans/skips.

HorizontalAlign

Defines how objects align horizontally in the chain

HorizontalChainReference

Represents a horizontal chain within a ConstraintLayout.

HorizontalChainScope
InvalidationStrategy

Provide different invalidation strategies for MotionLayout.

InvalidationStrategyScope

Helper scope that provides some strategies to improve performance based on incoming constraints.

KeyAttributeScope

Scope to define KeyFrame attributes.

KeyAttributesScope

Scope where multiple attribute KeyFrames may be defined.

KeyCycleScope

Scope to define cycling KeyFrames.

KeyCyclesScope

Scope where multiple cycling attribute KeyFrames may be defined.

KeyPositionScope

Scope to define KeyFrame positions.

KeyPositionsScope

Scope where multiple position KeyFrames may be defined.

LayoutReference

Represents a ConstraintLayout item that requires a unique identifier.

MotionLayoutScope
MotionLayoutScope.CustomProperties
MotionLayoutScope.MotionProperties
MotionSceneScope

Scope used by the MotionScene DSL.

MotionSceneScope.ConstrainedLayoutReferences
OnSwipe

Defines the OnSwipe behavior for a Transition.

RelativePosition

Relative coordinate space in which KeyPositions are applied.

Skip

Defines how many rows and/or columns to skip, starting from the given position.

Span

Defines the spanned area (that crosses multiple columns and/or rows) that a widget will take when placed at the given position.

SpringBoundary

Behavior of the spring as it crosses its target position.

State

The state of the ConstraintLayout solver.

SwipeDirection

Direction of the touch input that will initiate the swipe handling.

SwipeMode

Defines the type of motion used when animating during touch-up.

SwipeSide

Side of the bounds to track during touch handling, this is to account for when the widget changes size during the Transition.

SwipeTouchUp

The logic used to decide the target position when the touch input ends.

TransitionScope

Scope where Transition parameters are defined.

VerticalAlign

Defines how objects align vertically within the chain

VerticalChainReference

Represents a vertical chain within a ConstraintLayout.

VerticalChainScope
Visibility

The overall visibility of a widget in a ConstraintLayout.

Wrap

Wrap defines the type of chain

Objects

Annotations

Enums

LayoutInfoFlags
MotionLayoutDebugFlags
MotionLayoutFlag

This enum is deprecated. Unnecessary, MotionLayout remeasures when its content changes.

Top-level functions summary

inline Unit
@Composable
ConstraintLayout(
    modifier: Modifier,
    optimizationLevel: Int,
    animateChanges: Boolean,
    animationSpec: AnimationSpec<Float>,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
)

Layout that positions its children according to the constraints between them.

inline Unit
@Composable
ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier,
    optimizationLevel: Int,
    animateChanges: Boolean,
    animationSpec: AnimationSpec<Float>,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable () -> Unit
)

Layout that positions its children according to the constraints between them.

ConstraintSet

Creates a ConstraintSet with the constraints defined in the description block.

ConstraintSet
ConstraintSet(@Language(value = "json5") jsonContent: String)

Parses the given JSON5 into a ConstraintSet.

ConstraintSet
@Composable
ConstraintSet(
    @Language(value = "json5") content: String,
    @Language(value = "json5") overrideVariables: String?
)

Parses content into a ConstraintSet and sets the variables defined in the Variables block with the values of overrideVariables.

ConstraintSet
ConstraintSet(extendConstraintSet: ConstraintSet, description: ConstraintSetScope.() -> Unit)

Creates a ConstraintSet that extends the changes applied by extendConstraintSet.

ConstraintSet
ConstraintSet(
    extendConstraintSet: ConstraintSet,
    @Language(value = "json5") jsonContent: String
)

Creates a ConstraintSet from a jsonContent string that extends the changes applied by extendConstraintSet.

Unit
@Composable
ItemHolder(
    i: Int,
    slotPrefix: String,
    showSlot: Boolean,
    function: @Composable () -> Unit
)
Unit
@Composable
MotionCarousel(
    motionScene: MotionScene,
    initialSlotIndex: Int,
    numSlots: Int,
    backwardTransition: String,
    forwardTransition: String,
    slotPrefix: String,
    showSlots: Boolean,
    content: MotionCarouselScope.() -> Unit
)

Implements an horizontal Carousel of n elements, driven by drag gestures and customizable through a provided MotionScene.

inline Unit
@Composable
MotionLayout(
    motionScene: MotionScene,
    progress: Float,
    modifier: Modifier,
    transitionName: String,
    debugFlags: DebugFlags,
    optimizationLevel: Int,
    invalidationStrategy: InvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
)

Layout that can animate between multiple ConstraintSets as defined by Transitions in the given MotionScene.

inline Unit
@Composable
MotionLayout(
    motionScene: MotionScene,
    constraintSetName: String?,
    animationSpec: AnimationSpec<Float>,
    modifier: Modifier,
    noinline finishedAnimationListener: (() -> Unit)?,
    debugFlags: DebugFlags,
    optimizationLevel: Int,
    invalidationStrategy: InvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
)

Layout that can animate between multiple ConstraintSets as defined by Transitions in the given MotionScene.

inline Unit
@Composable
MotionLayout(
    start: ConstraintSet,
    end: ConstraintSet,
    progress: Float,
    modifier: Modifier,
    transition: Transition?,
    debugFlags: DebugFlags,
    optimizationLevel: Int,
    invalidationStrategy: InvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
)

Layout that can animate between two different layout states described in ConstraintSets.

MotionScene
@Composable
MotionScene(@Language(value = "json5") content: String)

Parses the given JSON5 into a MotionScene.

MotionScene
MotionScene(motionSceneContent: MotionSceneScope.() -> Unit)

Returns a MotionScene instance defined by motionSceneContent.

Transition
Transition(@Language(value = "json5") content: String)

Parses the given JSON5 into a Transition.

Transition
Transition(from: String, to: String, content: TransitionScope.() -> Unit)

Defines the interpolation parameters between the ConstraintSets to achieve fine-tuned animations.

Extension functions summary

Dimension.MaxCoercible

Sets the lower bound of the current Dimension to a fixed dp value.

Dimension

Sets the lower bound of the current Dimension to a fixed dp value.

Dimension

This function is deprecated. Unintended method name, use atLeast(dp) instead

Dimension.MinCoercible

Sets the upper bound of the current Dimension to a fixed dp value.

Dimension

Sets the upper bound of the current Dimension to a fixed dp value.

inline Unit
<T : Any?> MotionCarouselScope.items(
    items: List<T>,
    crossinline itemContent: @Composable (item) -> Unit
)
inline Unit
<T : Any?> MotionCarouselScope.itemsWithProperties(
    items: List<T>,
    crossinline itemContent: @Composable (item, properties: State<MotionLayoutScope.MotionProperties>) -> Unit
)
Modifier
Modifier.layoutId(layoutId: String, tag: String?)

Alternative to androidx.compose.ui.layout.layoutId that enables the use of tag.

Top-level properties summary

Extension properties summary

Dimension.MaxCoercible

Sets the lower bound of the current Dimension to be the wrap content size of the child.

Dimension

Sets the lower bound of the current Dimension to be the wrap content size of the child.

Dimension.MinCoercible

Sets the upper bound of the current Dimension to be the wrap content size of the child.

Dimension

Sets the upper bound of the current Dimension to be the WRAP_DIMENSION size of the child.

Any?
Any?

Top-level functions

@Composable
inline fun ConstraintLayout(
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChanges: Boolean = false,
    animationSpec: AnimationSpec<Float> = tween<Float>(),
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
): Unit

Layout that positions its children according to the constraints between them.

Constraints are defined within the content of this ConstraintLayout Composable.

Items in the layout that are to be constrained are initialized with ConstraintLayoutScope.createRef:

val textRef = createRef()
val imageRef = createRef()

You may also use ConstraintLayoutScope.createRefs to declare up to 16 items using the destructuring declaration pattern:

val (textRef, imageRef) = createRefs()

Individual constraints are defined with Modifier.constrainAs, this will also bind the Composable to the given ConstrainedLayoutReference.

So, a simple layout with a text in the middle and an image next to it may be declared like this (keep in mind, when using center..., start or end the layout direction will automatically change in RTL locales):

ConstraintLayout(Modifier.fillMaxSize()) {
val (textRef, imageRef) = createRefs()
Text(
modifier = Modifier.constrainAs(textRef) {
centerTo(parent)
},
text = "Hello, World!"
)
Image(
modifier = Modifier.constrainAs(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
},
imageVector = Icons.Default.Android,
contentDescription = null
)
}

See ConstrainScope to learn more about how to constrain elements together.

 

Helpers

You may also use helpers, a set of virtual (not shown on screen) components that provide special layout behaviors, you may find these in the ConstraintLayoutScope with the 'create...' prefix, a few of these are Guidelines, Chains and Barriers.

 

Guidelines

Lines to which other ConstrainedLayoutReferences may be constrained to, these are defined at either a fixed or percent position from an anchor of the ConstraintLayout parent (top, bottom, start, end, absoluteLeft, absoluteRight).

 

Example:

val (textRef) = createRefs()
val vG = createGuidelineFromStart(fraction = 0.3f)
Text(
modifier = Modifier.constrainAs(textRef) {
centerVerticallyTo(parent)
centerAround(vG)
},
text = "Hello, World!"
)

See

Chains

Chains may be either horizontal or vertical, these, take a set of ConstrainedLayoutReferences and create bi-directional constraints on each of them at the same orientation of the chain in the given order, meaning that an horizontal chain will create constraints between the start and end anchors.

The result, a layout that evenly distributes the space within its elements.

 

For example, to make a layout with three text elements distributed so that the spacing between them (and around them) is equal:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(textRef0, textRef1, textRef2, chainStyle = ChainStyle.Spread)

Text(modifier = Modifier.constrainAs(textRef0) {}, text = "Hello")
Text(modifier = Modifier.constrainAs(textRef1) {}, text = "Foo")
Text(modifier = Modifier.constrainAs(textRef2) {}, text = "Bar")

You may set margins within elements in a chain with ConstraintLayoutScope.withChainParams:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(
textRef0,
textRef1.withChainParams(startMargin = 100.dp, endMargin = 100.dp),
textRef2,
chainStyle = ChainStyle.Spread
)

Text(modifier = Modifier.constrainAs(textRef0) {}, text = "Hello")
Text(modifier = Modifier.constrainAs(textRef1) {}, text = "Foo")
Text(modifier = Modifier.constrainAs(textRef2) {}, text = "Bar")

You can also change the way space is distributed, as chains have three different styles:

  • ChainStyle.Spread Layouts are evenly distributed after margins are accounted for (the space around and between each item is even). This is the default style for chains.

  • ChainStyle.SpreadInside The first and last layouts are affixed to each end of the chain, and the rest of the items are evenly distributed (after margins are accounted for). I.e.: Items are spread from the inside, distributing the space between them with no space around the first and last items.

  • ChainStyle.Packed The layouts are packed together after margins are accounted for, by default, they're packed together at the middle, you can change this behavior with the bias parameter of ChainStyle.Packed.

  • Alternatively, you can make every Layout in the chain to be Dimension.fillToConstraints and then set a particular weight to each of them to create a weighted chain.

Weighted Chain

Weighted chains are useful when you want the size of the elements to depend on the remaining size of the chain. As opposed to just distributing the space around and/or in-between the items.

 

For example, to create a layout with three text elements in a row where each element takes the exact same size regardless of content, you can use a simple weighted chain where each item has the same weight:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(
textRef0.withChainParams(weight = 1f),
textRef1.withChainParams(weight = 1f),
textRef2.withChainParams(weight = 1f),
chainStyle = ChainStyle.Spread
)

Text(modifier = Modifier.background(Color.Cyan).constrainAs(textRef0) {
width = Dimension.fillToConstraints
}, text = "Hello, World!")
Text(modifier = Modifier.background(Color.Red).constrainAs(textRef1) {
width = Dimension.fillToConstraints
}, text = "Foo")
Text(modifier = Modifier.background(Color.Cyan).constrainAs(textRef2) {
width = Dimension.fillToConstraints
}, text = "This text is six words long")

This way, the texts will horizontally occupy the same space even if one of them is significantly larger than the others.

 

Keep in mind that chains have a relatively high performance cost. For example, if you plan on having multiple chains one below the other, consider instead, applying just one chain and using it as a reference to constrain all other elements to the ones that match their position in that one chain. It may provide increased performance with no significant changes in the layout output.

Alternatively, consider if other helpers such as ConstraintLayoutScope.createGrid can accomplish the same layout.

 

See

Barriers

Barriers take a set of ConstrainedLayoutReferences and creates the most further point in a given direction where other ConstrainedLayoutReference can constrain to.

 

This is useful in situations where elements in a layout may have different sizes but you want to always constrain to the largest item, for example, if you have a text element on top of another and want an image to always be constrained to the end of them:

val (textRef0, textRef1, imageRef) = createRefs()

// Creates a point at the furthest end anchor from the elements in the barrier
val endTextsBarrier = createEndBarrier(textRef0, textRef1)

Text(
modifier = Modifier.constrainAs(textRef0) {
centerTo(parent)
},
text = "Hello, World!"
)
Text(
modifier = Modifier.constrainAs(textRef1) {
top.linkTo(textRef0.bottom)
start.linkTo(textRef0.start)
},
text = "Foo Bar"
)
Image(
modifier = Modifier.constrainAs(imageRef) {
top.linkTo(textRef0.top)
bottom.linkTo(textRef1.bottom)

// Image will always be at the end of both texts, regardless of their size
start.linkTo(endTextsBarrier, margin = 8.dp)
},
imageVector = Icons.Default.Android,
contentDescription = null
)

Be careful not to constrain a ConstrainedLayoutReference to a barrier that references it or that depends on it indirectly. This creates a cyclic dependency that results in unsupported layout behavior.

See

 

Tip: If you notice that you are creating many different constraints based on State variables or configuration changes, consider using the ConstraintSet pattern instead, makes it clearer to distinguish different layouts and allows you to automatically animate the layout when the provided ConstraintSet is different.

Parameters
modifier: Modifier = Modifier

Modifier to apply to this layout node.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization flags for ConstraintLayout. The default is Optimizer.OPTIMIZATION_STANDARD.

animateChanges: Boolean = false

When enabled, ConstraintLayout will animate the layout if there were any changes on the constraints during recomposition. If there's a change while the layout is still animating the current animation will always complete before animating to the latest changes.

animationSpec: AnimationSpec<Float> = tween<Float>()

The AnimationSpec used for animateChanges. tween by default.

noinline finishedAnimationListener: (() -> Unit)? = null

Lambda called whenever an animation due to animateChanges finishes.

crossinline content: @Composable ConstraintLayoutScope.() -> Unit

Content of this layout node.

@Composable
inline fun ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChanges: Boolean = false,
    animationSpec: AnimationSpec<Float> = tween<Float>(),
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable () -> Unit
): Unit

Layout that positions its children according to the constraints between them.

This Composable of ConstraintLayout takes a ConstraintSet where the layout is defined using references and constraints.

Layouts referenced in the given constraintSet can be bound to immediate child Composables using Modifier.layoutId, where the given layoutIds match each named reference.

 

So, a simple layout with a text in the middle and an image next to it may be declared like this:

// IDs
val textId = "text"
val imageId = "image"

// Layout definition with references and constraints
val constraintSet = remember {
ConstraintSet {
val (textRef, imageRef) = createRefsFor(textId, imageId)
constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}
}
}

// ConstraintLayout uses our given ConstraintSet
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier.fillMaxSize()
) {
// References are bound to Composables using Modifier.layoutId(Any)
Text(
modifier = Modifier.layoutId(textId),
text = "Hello, World!"
)
Image(
modifier = Modifier.layoutId(imageId),
imageVector = Icons.Default.Android,
contentDescription = null
)
}

See ConstraintSet to learn more on how to declare layouts using constraints.

 

Handling of ConstraintSet objects

You typically want to remember declared ConstraintSets, to avoid unnecessary allocations on recomposition, if the ConstraintSetScope block consumes any State variables, then something like remember { derivedStateOf { ConstraintSet { ... } } } would be more appropriate.

 

However, note in the example above that our ConstraintSet is constant, so we can declare it at a top level, improving overall Composition performance:

private const val TEXT_ID = "text"
private const val IMAGE_ID = "image"
private val mConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (textRef, imageRef) = createRefsFor(TEXT_ID, IMAGE_ID)
constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}
}
}

@Preview
@Composable
fun ConstraintSetExample() {
ConstraintLayout(
constraintSet = mConstraintSet,
modifier = Modifier.fillMaxSize()
) {
Text(
modifier = Modifier.layoutId(TEXT_ID),
text = "Hello, World!"
)
Image(
modifier = Modifier.layoutId(IMAGE_ID),
imageVector = Icons.Default.Android,
contentDescription = null
)
}
}

This pattern (as opposed to defining constraints with ConstraintLayoutScope.constrainAs) is preferred when you want different layouts to be produced on different State variables or configuration changes. As it makes it easier to create distinguishable layouts, for example when building adaptive layouts based on Window size class:

private const val NAV_BAR_ID = "navBar"
private const val CONTENT_ID = "content"

private val compactConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (navBarRef, contentRef) = createRefsFor(NAV_BAR_ID, CONTENT_ID)

// Navigation bar at the bottom for Compact devices
constrain(navBarRef) {
width = Dimension.percent(1f)
height = 40.dp.asDimension()
bottom.linkTo(parent.bottom)
}

constrain(contentRef) {
width = Dimension.percent(1f)
height = Dimension.fillToConstraints

top.linkTo(parent.top)
bottom.linkTo(navBarRef.top)
}
}
}

private val mediumConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (navBarRef, contentRef) = createRefsFor(NAV_BAR_ID, CONTENT_ID)

// Navigation bar at the start on Medium class devices
constrain(navBarRef) {
width = 40.dp.asDimension()
height = Dimension.percent(1f)

start.linkTo(parent.start)
}

constrain(contentRef) {
width = Dimension.fillToConstraints
height = Dimension.percent(1f)

start.linkTo(navBarRef.end)
end.linkTo(parent.end)
}
}
}

@Composable
fun MyAdaptiveLayout(
windowWidthSizeClass: WindowWidthSizeClass
) {
val constraintSet = if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
compactConstraintSet
}
else {
mediumConstraintSet
}
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier.fillMaxSize()
) {
Box(Modifier.background(Color.Blue).layoutId(NAV_BAR_ID))
Box(Modifier.background(Color.Red).layoutId(CONTENT_ID))
}
}

Animate Changes

At this point, you may also use the animateChanges flag to animate the layout changes. This is triggered whenever a different (by equality) constraintSet is provided on recomposition. And, is driven by animationSpec, finishedAnimationListener is called whenever a layout animation ends.

On the example above, using animateChanges would result on the layout being animated when the device changes to non-compact window class, typical behavior in some Foldable devices.

 

If more control is needed, we recommend using MotionLayout instead, which has a very similar pattern through the MotionScene object.

Parameters
constraintSet: ConstraintSet

The ConstraintSet that describes the expected layout, defined references should be bound to Composables with Modifier.layoutId.

modifier: Modifier = Modifier

Modifier to apply to this layout node.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization flags for ConstraintLayout. The default is Optimizer.OPTIMIZATION_STANDARD.

animateChanges: Boolean = false

When enabled, ConstraintLayout will animate the layout if there were any changes on the constraints during recomposition. If there's a change while the layout is still animating the current animation will always complete before animating to the latest changes.

animationSpec: AnimationSpec<Float> = tween<Float>()

The AnimationSpec used for animateChanges. tween by default.

noinline finishedAnimationListener: (() -> Unit)? = null

Lambda called whenever an animation due to animateChanges finishes.

crossinline content: @Composable () -> Unit

Content of this layout node.

fun ConstraintSet(description: ConstraintSetScope.() -> Unit): ConstraintSet

Creates a ConstraintSet with the constraints defined in the description block.

See ConstraintSet to learn how to define constraints.

fun ConstraintSet(@Language(value = "json5") jsonContent: String): ConstraintSet

Parses the given JSON5 into a ConstraintSet.

See the official Github Wiki to learn the syntax.

@Composable
fun ConstraintSet(
    @Language(value = "json5") content: String,
    @Language(value = "json5") overrideVariables: String? = null
): ConstraintSet

Parses content into a ConstraintSet and sets the variables defined in the Variables block with the values of overrideVariables.

Eg:

For Variables: { margin: { from: 'initialMargin', step: 10 } }

overrideVariables = "{ 'initialMargin' = 50 }"

Will create a ConstraintSet where initialMargin is 50.

fun ConstraintSet(extendConstraintSet: ConstraintSet, description: ConstraintSetScope.() -> Unit): ConstraintSet

Creates a ConstraintSet that extends the changes applied by extendConstraintSet.

See ConstraintSet to learn how to define constraints.

fun ConstraintSet(
    extendConstraintSet: ConstraintSet,
    @Language(value = "json5") jsonContent: String
): ConstraintSet

Creates a ConstraintSet from a jsonContent string that extends the changes applied by extendConstraintSet.

@Composable
fun ItemHolder(
    i: Int,
    slotPrefix: String,
    showSlot: Boolean,
    function: @Composable () -> Unit
): Unit
@Composable
fun MotionCarousel(
    motionScene: MotionScene,
    initialSlotIndex: Int,
    numSlots: Int,
    backwardTransition: String = "backward",
    forwardTransition: String = "forward",
    slotPrefix: String = "slot",
    showSlots: Boolean = false,
    content: MotionCarouselScope.() -> Unit
): Unit

Implements an horizontal Carousel of n elements, driven by drag gestures and customizable through a provided MotionScene.

Usage

-----

val cardsExample = arrayListOf(...)

MotionCarousel(motionScene...) { items(cardsExample) { card -> SomeCardComponent(card) } }

or if wanting to use parameters in your components that are defined in the MotionScene:

MotionCarousel(motionScene...) { itemsWithProperties(cardsExample) { card, properties -> SomeCardComponent(card, properties) } }

Note

----

It is recommended to encapsulate the usage of MotionCarousel:

fun MyCarousel(content: MotionCarouselScope.() -> Unit) { val motionScene = ... MotionCarousel(motionScene..., content) }

Mechanism overview and MotionScene architecture

-----------------------------------------------

We use 3 different states to represent the Carousel: "previous", "start", and "next". A horizontal swipe gesture will transition from one state to the other, e.g. a right to left swipe will transition from "start" to "next".

We consider a scene containing several "slots" for the elements we want to display in the Carousel. In an horizontal carousel, the easiest way to think of them is as an horizontal list of slots.

The overall mechanism thus works by moving those "slots" according to the gesture, and then mapping the Carousel's elements to the corresponding slots as we progress through the list of elements.

For example, let's consider using a Carousel with 3 slots 1 and 2, with 1 the center slot being the only visible one during the initial state "start" (| and | representing the screen borders) and 0 being outside of the screen on the left and 2 outside of the screen on the right:

start 0 | 1 | 2

We can setup the previous state in the following way:

previous | 0 | 3

And the next state like:

next 1 | 2 |

All three states together allowing to implement the Carousel motion we are looking for:

previous | 0 | 3 start 0 | 1 | 2 next 1 | 2 |

At the end of the swipe gesture, we instantly move back to the start state:

start 0 | 1 | 2 -> gesture starts next 1 | 2 | -> gesture ends start 0 | 1 | 2 -> instant snap back to start state

After the instant snap, we update the elements actually displayed in the slots. For example, we can start with the elements {a}, {b} and {c} assigned respectively to the slots 0, 1 and 2. After the swipe the slots will be reassigned to {b}, {c} and {d}:

start 0:{a} | 1:{b} | 2:{d} -> gesture starts next 0:{a} 1:{b} | 2:{c} | -> gesture ends start 0:{a} | 1:{b} | 2:{c} -> instant snap back to start state start 0:{b} | 1:{c} | 2:{d} -> repaint with reassigned elements

In this manner, the overall effect emulate an horizontal scroll of a list of elements.

A similar mechanism is applied the left to right gesture going through the previous state.

Starting slot

-------------

In order to operate, we need a list of slots. We retrieve them from the motionScene by adding to the slotPrefix an index number. As the starting slot may not be the first one in the scene, we also need to be able to specify a startIndex.

Note that at the beginning of the Carousel, we will not populate the slots that have a lower index than startIndex, and at the end of the Carousel, we will not populate the slots that have a higher index than startIndex.

Parameters
initialSlotIndex: Int

the slot index that holds the current element

numSlots: Int

the number of slots in the scene

backwardTransition: String = "backward"

the name of the previous transition (default "previous")

forwardTransition: String = "forward"

the name of the next transition (default "next")

slotPrefix: String = "slot"

the prefix used for the slots widgets in the scene (default "card")

showSlots: Boolean = false

a debug flag to display the slots in the scene regardless if they are populated

content: MotionCarouselScope.() -> Unit

the MotionCarouselScope we use to map the elements to the slots

@Composable
inline fun MotionLayout(
    motionScene: MotionScene,
    progress: Float,
    modifier: Modifier = Modifier,
    transitionName: String = "default",
    debugFlags: DebugFlags = DebugFlags.None,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
): Unit

Layout that can animate between multiple ConstraintSets as defined by Transitions in the given MotionScene.

 

The animation is driven by the progress value, so it will typically be a result of using an Animatable or animateFloatAsState:

var animateToEnd by remember { mutableStateOf(false) }
MotionLayout(
motionScene = MotionScene {
val buttonRef = createRefFor("button")
defaultTransition(
from = constraintSet {
constrain(buttonRef) {
top.linkTo(parent.top)
}
},
to = constraintSet {
constrain(buttonRef) {
bottom.linkTo(parent.bottom)
}
}
)
},
progress = animateFloatAsState(if (animateToEnd) 1f else 0f).value,
modifier = Modifier.fillMaxSize()
) {
Button(onClick = { animateToEnd = !animateToEnd }, Modifier.layoutId("button")) {
Text("Hello, World!")
}
}

Note that you must use Modifier.layoutId to bind the the references used in the ConstraintSets to the Composable.

Parameters
motionScene: MotionScene

Holds all the layout states defined in ConstraintSets and the interpolation associated between them (known as Transitions).

progress: Float

Sets the interpolated position of the layout between the ConstraintSets.

modifier: Modifier = Modifier

Modifier to apply to this layout node.

transitionName: String = "default"

The name of the transition to apply on the layout. By default, it will target the transition defined with MotionSceneScope.defaultTransition.

debugFlags: DebugFlags = DebugFlags.None

Flags to enable visual debugging. DebugFlags.None by default.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization parameter for the underlying ConstraintLayout, Optimizer.OPTIMIZATION_STANDARD by default.

invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy

Provides strategies to optimize invalidations in MotionLayout. Excessive invalidations will be the typical cause of bad performance in MotionLayout. See InvalidationStrategy to learn how to apply common strategies.

crossinline content: @Composable MotionLayoutScope.() -> Unit

The content to be laid out by MotionLayout, note that each layout Composable should be bound to an ID defined in the ConstraintSets using Modifier.layoutId.

@Composable
inline fun MotionLayout(
    motionScene: MotionScene,
    constraintSetName: String?,
    animationSpec: AnimationSpec<Float>,
    modifier: Modifier = Modifier,
    noinline finishedAnimationListener: (() -> Unit)? = null,
    debugFlags: DebugFlags = DebugFlags.None,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
): Unit

Layout that can animate between multiple ConstraintSets as defined by Transitions in the given MotionScene.

 

The animation is driven based on the given constraintSetName. During recomposition, MotionLayout will interpolate from whichever ConstraintSet it currently is, to the one corresponding to constraintSetName. So, a null constraintSetName will result in no changes.

var name by remember { mutableStateOf(0) }
MotionLayout(
motionScene = MotionScene {
val buttonRef = createRefFor("button")
val initialStart = constraintSet("0") {
constrain(buttonRef) {
centerHorizontallyTo(parent, bias = 0f)
centerVerticallyTo(parent, bias = 0f)
}
}
val initialEnd = constraintSet("1") {
constrain(buttonRef) {
centerHorizontallyTo(parent, bias = 0f)
centerVerticallyTo(parent, bias = 1f)
}
}
constraintSet("2") {
constrain(buttonRef) {
centerHorizontallyTo(parent, bias = 1f)
centerVerticallyTo(parent, bias = 0f)
}
}
constraintSet("3") {
constrain(buttonRef) {
centerHorizontallyTo(parent, bias = 1f)
centerVerticallyTo(parent, bias = 1f)
}
}
// We need at least the default transition to define the initial state
defaultTransition(initialStart, initialEnd)
},
constraintSetName = name.toString(),
animationSpec = tween(1200),
modifier = Modifier.fillMaxSize()
) {
// Switch to a random ConstraintSet on click
Button(onClick = { name = IntRange(0, 3).random() }, Modifier.layoutId("button")) {
Text("Hello, World!")
}
}

Animations are run one after the other, if multiple are queued, only the last one will be executed. You may use finishedAnimationListener to know whenever an animation is finished.

Parameters
motionScene: MotionScene

Holds all the layout states defined in ConstraintSets and the interpolation associated between them (known as Transitions).

constraintSetName: String?

The name of the ConstraintSet to animate to. Null for no animation.

animationSpec: AnimationSpec<Float>

Specifies how the internal progress value is animated.

modifier: Modifier = Modifier

Modifier to apply to this layout node.

noinline finishedAnimationListener: (() -> Unit)? = null

Called when an animation triggered by a change in constraintSetName has ended.

debugFlags: DebugFlags = DebugFlags.None

Flags to enable visual debugging. DebugFlags.None by default.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization parameter for the underlying ConstraintLayout, Optimizer.OPTIMIZATION_STANDARD by default.

invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy

Provides strategies to optimize invalidations in MotionLayout. Excessive invalidations will be the typical cause of bad performance in MotionLayout. See InvalidationStrategy to learn how to apply common strategies.

crossinline content: @Composable MotionLayoutScope.() -> Unit

The content to be laid out by MotionLayout, note that each layout Composable should be bound to an ID defined in the ConstraintSets using Modifier.layoutId.

@Composable
inline fun MotionLayout(
    start: ConstraintSet,
    end: ConstraintSet,
    progress: Float,
    modifier: Modifier = Modifier,
    transition: Transition? = null,
    debugFlags: DebugFlags = DebugFlags.None,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
): Unit

Layout that can animate between two different layout states described in ConstraintSets.

 

The animation is driven by the progress value, so it will typically be a result of using an Animatable or animateFloatAsState:

var animateToEnd by remember { mutableStateOf(false) }
MotionLayout(
start = ConstraintSet {
constrain(createRefFor("button")) {
top.linkTo(parent.top)
}
},
end = ConstraintSet {
constrain(createRefFor("button")) {
bottom.linkTo(parent.bottom)
}
},
progress = animateFloatAsState(if (animateToEnd) 1f else 0f).value,
modifier = Modifier.fillMaxSize()
) {
Button(onClick = { animateToEnd = !animateToEnd }, Modifier.layoutId("button")) {
Text("Hello, World!")
}
}

Note that you must use Modifier.layoutId to bind the the references used in the ConstraintSets to the Composable.

Parameters
start: ConstraintSet

ConstraintSet that defines the layout at 0f progress.

end: ConstraintSet

ConstraintSet that defines the layout at 1f progress.

progress: Float

Sets the interpolated position of the layout between the ConstraintSets.

modifier: Modifier = Modifier

Modifier to apply to this layout node.

transition: Transition? = null

Defines the interpolation parameters between the ConstraintSets to achieve fine-tuned animations.

debugFlags: DebugFlags = DebugFlags.None

Flags to enable visual debugging. DebugFlags.None by default.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization parameter for the underlying ConstraintLayout, Optimizer.OPTIMIZATION_STANDARD by default.

invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy

Provides strategies to optimize invalidations in MotionLayout. Excessive invalidations will be the typical cause of bad performance in MotionLayout. See InvalidationStrategy to learn how to apply common strategies.

crossinline content: @Composable MotionLayoutScope.() -> Unit

The content to be laid out by MotionLayout, note that each layout Composable should be bound to an ID defined in the ConstraintSets using Modifier.layoutId.

@Composable
fun MotionScene(@Language(value = "json5") content: String): MotionScene

Parses the given JSON5 into a MotionScene.

See the official Github Wiki to learn the syntax.

fun Transition(@Language(value = "json5") content: String): Transition

Parses the given JSON5 into a Transition.

See the official Github Wiki to learn the syntax.

fun Transition(from: String = "start", to: String = "end", content: TransitionScope.() -> Unit): Transition

Defines the interpolation parameters between the ConstraintSets to achieve fine-tuned animations.

Parameters
from: String = "start"

The name of the initial ConstraintSet. Should correspond to a named ConstraintSet when added as part of a MotionScene with MotionSceneScope.addTransition.

to: String = "end"

The name of the target ConstraintSet. Should correspond to a named ConstraintSet when added as part of a MotionScene with MotionSceneScope.addTransition.

content: TransitionScope.() -> Unit

Lambda to define the Transition parameters on the given TransitionScope.

Extension functions

fun Dimension.Coercible.atLeast(dp: Dp): Dimension.MaxCoercible

Sets the lower bound of the current Dimension to a fixed dp value.

fun Dimension.MinCoercible.atLeast(dp: Dp): Dimension

Sets the lower bound of the current Dimension to a fixed dp value.

fun Dimension.MinCoercible.atLeastWrapContent(dp: Dp): Dimension

Sets the lower bound of the current Dimension to a fixed dp value.

fun Dimension.Coercible.atMost(dp: Dp): Dimension.MinCoercible

Sets the upper bound of the current Dimension to a fixed dp value.

fun Dimension.MaxCoercible.atMost(dp: Dp): Dimension

Sets the upper bound of the current Dimension to a fixed dp value.

inline fun <T : Any?> MotionCarouselScope.items(
    items: List<T>,
    crossinline itemContent: @Composable (item) -> Unit
): Unit
inline fun <T : Any?> MotionCarouselScope.itemsWithProperties(
    items: List<T>,
    crossinline itemContent: @Composable (item, properties: State<MotionLayoutScope.MotionProperties>) -> Unit
): Unit
fun Modifier.layoutId(layoutId: String, tag: String? = null): Modifier

Alternative to androidx.compose.ui.layout.layoutId that enables the use of tag.

Parameters
layoutId: String

The unique Id string assigned to the Composable

tag: String? = null

A string to represent a group of Composables that may be affected by a ConstraintLayout function. Eg: The Variables block in a JSON5 based ConstraintSet

Top-level properties

Extension properties

val Dimension.Coercible.atLeastWrapContentDimension.MaxCoercible

Sets the lower bound of the current Dimension to be the wrap content size of the child.

val Dimension.MinCoercible.atLeastWrapContentDimension

Sets the lower bound of the current Dimension to be the wrap content size of the child.

val Dimension.Coercible.atMostWrapContentDimension.MinCoercible

Sets the upper bound of the current Dimension to be the wrap content size of the child.

val Dimension.MaxCoercible.atMostWrapContentDimension

Sets the upper bound of the current Dimension to be the WRAP_DIMENSION size of the child.