/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.constraintlayout.compose
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.core.parser.CLArray
import androidx.constraintlayout.core.parser.CLElement
import androidx.constraintlayout.core.parser.CLNumber
import androidx.constraintlayout.core.parser.CLObject
import androidx.constraintlayout.core.parser.CLString
import androidx.constraintlayout.core.state.ConstraintSetParser
/**
* Common scope for [ConstraintLayoutScope] and [ConstraintSetScope], the content being shared
* between the inline DSL API and the ConstraintSet-based API.
*/
abstract class ConstraintLayoutBaseScope internal constructor(extendFrom: CLObject?) {
@Suppress("unused") // Needed to maintain binary compatibility
constructor() : this(null)
@Deprecated("Tasks is unused, it breaks the immutability promise.")
protected val tasks = mutableListOf<(State) -> Unit>()
internal val containerObject: CLObject = extendFrom?.clone() ?: CLObject(charArrayOf())
fun applyTo(state: State) {
ConstraintSetParser.populateState(
containerObject,
state,
ConstraintSetParser.LayoutVariables()
)
}
open fun reset() {
containerObject.clear()
helperId = HelpersStartId
helpersHashCode = 0
}
@PublishedApi
internal var helpersHashCode: Int = 0
private fun updateHelpersHashCode(value: Int) {
helpersHashCode = (helpersHashCode * 1009 + value) % 1000000007
}
private val HelpersStartId = 1000
private var helperId = HelpersStartId
private fun createHelperId() = helperId++
/**
* Represents a vertical anchor (e.g. start/end of a layout, guideline) that layouts
* can link to in their `Modifier.constrainAs` or `constrain` blocks.
*
* @param reference The [LayoutReference] that this anchor belongs to.
*/
@Stable
data class VerticalAnchor internal constructor(
internal val id: Any,
internal val index: Int,
val reference: LayoutReference
)
/**
* Represents a horizontal anchor (e.g. top/bottom of a layout, guideline) that layouts
* can link to in their `Modifier.constrainAs` or `constrain` blocks.
*
* @param reference The [LayoutReference] that this anchor belongs to.
*/
@Stable
data class HorizontalAnchor internal constructor(
internal val id: Any,
internal val index: Int,
val reference: LayoutReference
)
/**
* Represents a horizontal anchor corresponding to the [FirstBaseline] of a layout that other
* layouts can link to in their `Modifier.constrainAs` or `constrain` blocks.
*
* @param reference The [LayoutReference] that this anchor belongs to.
*/
// TODO(popam): investigate if this can be just a HorizontalAnchor
@Stable
data class BaselineAnchor internal constructor(
internal val id: Any,
val reference: LayoutReference
)
/**
* Specifies additional constraints associated to the horizontal chain identified with [ref].
*/
fun constrain(
ref: HorizontalChainReference,
constrainBlock: HorizontalChainScope.() -> Unit
): HorizontalChainScope =
HorizontalChainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
/**
* Specifies additional constraints associated to the vertical chain identified with [ref].
*/
fun constrain(
ref: VerticalChainReference,
constrainBlock: VerticalChainScope.() -> Unit
): VerticalChainScope = VerticalChainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
/**
* Specifies the constraints associated to the layout identified with [ref].
*/
fun constrain(
ref: ConstrainedLayoutReference,
constrainBlock: ConstrainScope.() -> Unit
): ConstrainScope = ConstrainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
/**
* Convenient way to apply the same constraints to multiple [ConstrainedLayoutReference]s.
*/
fun constrain(
vararg refs: ConstrainedLayoutReference,
constrainBlock: ConstrainScope.() -> Unit
) {
refs.forEach { ref ->
constrain(ref, constrainBlock)
}
}
/**
* Creates a guideline at a specific offset from the start of the [ConstraintLayout].
*/
fun createGuidelineFromStart(offset: Dp): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "vGuideline")
putNumber("start", offset.value)
}
updateHelpersHashCode(1)
updateHelpersHashCode(offset.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a specific offset from the left of the [ConstraintLayout].
*/
fun createGuidelineFromAbsoluteLeft(offset: Dp): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "vGuideline")
putNumber("left", offset.value)
}
updateHelpersHashCode(2)
updateHelpersHashCode(offset.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a specific offset from the start of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the start of the [ConstraintLayout], while 1f will
* correspond to the end.
*/
fun createGuidelineFromStart(fraction: Float): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val percentParams = CLArray(charArrayOf()).apply {
add(CLString.from("start"))
add(CLNumber(fraction))
}
ref.asCLContainer().apply {
putString("type", "vGuideline")
put("percent", percentParams)
}
updateHelpersHashCode(3)
updateHelpersHashCode(fraction.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a width fraction from the left of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the left of the [ConstraintLayout], while 1f will
* correspond to the right.
*/
fun createGuidelineFromAbsoluteLeft(fraction: Float): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "vGuideline")
putNumber("percent", fraction)
}
updateHelpersHashCode(4)
updateHelpersHashCode(fraction.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a specific offset from the end of the [ConstraintLayout].
*/
fun createGuidelineFromEnd(offset: Dp): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "vGuideline")
putNumber("end", offset.value)
}
updateHelpersHashCode(5)
updateHelpersHashCode(offset.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a specific offset from the right of the [ConstraintLayout].
*/
fun createGuidelineFromAbsoluteRight(offset: Dp): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "vGuideline")
putNumber("right", offset.value)
}
updateHelpersHashCode(6)
updateHelpersHashCode(offset.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a width fraction from the end of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the end of the [ConstraintLayout], while 1f will
* correspond to the start.
*/
fun createGuidelineFromEnd(fraction: Float): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val percentParams = CLArray(charArrayOf()).apply {
add(CLString.from("end"))
add(CLNumber(fraction))
}
ref.asCLContainer().apply {
putString("type", "vGuideline")
put("percent", percentParams)
}
updateHelpersHashCode(3)
updateHelpersHashCode(fraction.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a width fraction from the right of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the right of the [ConstraintLayout], while 1f will
* correspond to the left.
*/
fun createGuidelineFromAbsoluteRight(fraction: Float): VerticalAnchor {
return createGuidelineFromAbsoluteLeft(1f - fraction)
}
/**
* Creates a guideline at a specific offset from the top of the [ConstraintLayout].
*/
fun createGuidelineFromTop(offset: Dp): HorizontalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "hGuideline")
putNumber("start", offset.value)
}
updateHelpersHashCode(7)
updateHelpersHashCode(offset.hashCode())
return HorizontalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a height fraction from the top of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the top of the [ConstraintLayout], while 1f will
* correspond to the bottom.
*/
fun createGuidelineFromTop(fraction: Float): HorizontalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "hGuideline")
putNumber("percent", fraction)
}
updateHelpersHashCode(8)
updateHelpersHashCode(fraction.hashCode())
return HorizontalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a specific offset from the bottom of the [ConstraintLayout].
*/
fun createGuidelineFromBottom(offset: Dp): HorizontalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
ref.asCLContainer().apply {
putString("type", "hGuideline")
putNumber("end", offset.value)
}
updateHelpersHashCode(9)
updateHelpersHashCode(offset.hashCode())
return HorizontalAnchor(ref.id, 0, ref)
}
/**
* Creates a guideline at a height percenide from the bottom of the [ConstraintLayout].
* A [fraction] of 0f will correspond to the bottom of the [ConstraintLayout], while 1f will
* correspond to the top.
*/
fun createGuidelineFromBottom(fraction: Float): HorizontalAnchor {
return createGuidelineFromTop(1f - fraction)
}
/**
* Creates and returns a start barrier, containing the specified elements.
*/
fun createStartBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "start")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(10)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates and returns a left barrier, containing the specified elements.
*/
fun createAbsoluteLeftBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "left")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(11)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates and returns a top barrier, containing the specified elements.
*/
fun createTopBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): HorizontalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "top")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(12)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return HorizontalAnchor(ref.id, 0, ref)
}
/**
* Creates and returns an end barrier, containing the specified elements.
*/
fun createEndBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "end")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(13)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates and returns a right barrier, containing the specified elements.
*/
fun createAbsoluteRightBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): VerticalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "right")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(14)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return VerticalAnchor(ref.id, 0, ref)
}
/**
* Creates and returns a bottom barrier, containing the specified elements.
*/
fun createBottomBarrier(
vararg elements: LayoutReference,
margin: Dp = 0.dp
): HorizontalAnchor {
val ref = LayoutReferenceImpl(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
ref.asCLContainer().apply {
putString("type", "barrier")
putString("direction", "bottom")
putNumber("margin", margin.value)
put("contains", elementArray)
}
updateHelpersHashCode(15)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(margin.hashCode())
return HorizontalAnchor(ref.id, 0, ref)
}
/**
* Flow helpers allows a long sequence of Composable widgets to wrap onto
* multiple rows or columns.
*
* @param elements [LayoutReference]s to be laid out by the Flow helper
* @param flowVertically if set to true arranges the Composables from top to bottom.
* Normally they are arranged from left to right.
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param maxElement defines the maximum element on a row before it if the
* @param padding sets padding around the content
* @param wrapMode sets the way reach maxElements is handled
* [Wrap.None] (default) -- no wrap behavior,
* [Wrap.Chain] - create additional chains
* @param verticalAlign set the way elements are aligned vertically. Center is default
* @param horizontalAlign set the way elements are aligned horizontally. Center is default
* @param horizontalFlowBias set the way elements are aligned vertically Center is default
* @param verticalFlowBias sets the top bottom bias of the vertical chain
* @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
* @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or SpreadInside)
*/
fun createFlow(
vararg elements: LayoutReference?,
flowVertically: Boolean = false,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
maxElement: Int = 0, // TODO: shouldn't this be -1? (aka: UNKNOWN)?
padding: Dp = 0.dp,
wrapMode: Wrap = Wrap.None,
verticalAlign: VerticalAlign = VerticalAlign.Center,
horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
horizontalFlowBias: Float = 0.0f,
verticalFlowBias: Float = 0.0f,
verticalStyle: FlowStyle = FlowStyle.Packed,
horizontalStyle: FlowStyle = FlowStyle.Packed,
): ConstrainedLayoutReference {
return createFlow(
elements = elements,
flowVertically = flowVertically,
verticalGap = verticalGap,
horizontalGap = horizontalGap,
maxElement = maxElement,
paddingLeft = padding,
paddingTop = padding,
paddingRight = padding,
paddingBottom = padding,
wrapMode = wrapMode,
verticalAlign = verticalAlign,
horizontalAlign = horizontalAlign,
horizontalFlowBias = horizontalFlowBias,
verticalFlowBias = verticalFlowBias,
verticalStyle = verticalStyle,
horizontalStyle = horizontalStyle
)
}
/**
* Flow helpers allows a long sequence of Composable widgets to wrap onto
* multiple rows or columns.
*
* @param elements [LayoutReference]s to be laid out by the Flow helper
* @param flowVertically if set to true aranges the Composables from top to bottom.
* Normally they are arranged from left to right.
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param maxElement defines the maximum element on a row before it if the
* @param paddingHorizontal sets paddingLeft and paddingRight of the content
* @param paddingVertical sets paddingTop and paddingBottom of the content
* @param wrapMode sets the way reach maxElements is handled
* [Wrap.None] (default) -- no wrap behavior,
* [Wrap.Chain] - create additional chains
* @param verticalAlign set the way elements are aligned vertically. Center is default
* @param horizontalAlign set the way elements are aligned horizontally. Center is default
* @param horizontalFlowBias set the way elements are aligned vertically Center is default
* @param verticalFlowBias sets the top bottom bias of the vertical chain
* @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
* @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or SpreadInside)
*/
fun createFlow(
vararg elements: LayoutReference?,
flowVertically: Boolean = false,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
maxElement: Int = 0,
paddingHorizontal: Dp = 0.dp,
paddingVertical: Dp = 0.dp,
wrapMode: Wrap = Wrap.None,
verticalAlign: VerticalAlign = VerticalAlign.Center,
horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
horizontalFlowBias: Float = 0.0f,
verticalFlowBias: Float = 0.0f,
verticalStyle: FlowStyle = FlowStyle.Packed,
horizontalStyle: FlowStyle = FlowStyle.Packed,
): ConstrainedLayoutReference {
return createFlow(
elements = elements,
flowVertically = flowVertically,
verticalGap = verticalGap,
horizontalGap = horizontalGap,
maxElement = maxElement,
paddingLeft = paddingHorizontal,
paddingTop = paddingVertical,
paddingRight = paddingHorizontal,
paddingBottom = paddingVertical,
wrapMode = wrapMode,
verticalAlign = verticalAlign,
horizontalAlign = horizontalAlign,
horizontalFlowBias = horizontalFlowBias,
verticalFlowBias = verticalFlowBias,
verticalStyle = verticalStyle,
horizontalStyle = horizontalStyle
)
}
/**
* Flow helpers allows a long sequence of Composable widgets to wrap onto
* multiple rows or columns.
*
* @param elements [LayoutReference]s to be laid out by the Flow helper
* @param flowVertically if set to true aranges the Composables from top to bottom.
* Normally they are arranged from left to right.
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param maxElement defines the maximum element on a row before it if the
* @param paddingLeft sets paddingLeft of the content
* @param paddingTop sets paddingTop of the content
* @param paddingRight sets paddingRight of the content
* @param paddingBottom sets paddingBottom of the content
* @param wrapMode sets the way reach maxElements is handled
* [Wrap.None] (default) -- no wrap behavior,
* [Wrap.Chain] - create additional chains
* @param verticalAlign set the way elements are aligned vertically. Center is default
* @param horizontalAlign set the way elements are aligned horizontally. Center is default
* @param horizontalFlowBias set the way elements are aligned vertically Center is default
* @param verticalFlowBias sets the top bottom bias of the vertical chain
* @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
* @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or SpreadInside)
*/
fun createFlow(
vararg elements: LayoutReference?,
flowVertically: Boolean = false,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
maxElement: Int = 0,
paddingLeft: Dp = 0.dp,
paddingTop: Dp = 0.dp,
paddingRight: Dp = 0.dp,
paddingBottom: Dp = 0.dp,
wrapMode: Wrap = Wrap.None,
verticalAlign: VerticalAlign = VerticalAlign.Center,
horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
horizontalFlowBias: Float = 0.0f,
verticalFlowBias: Float = 0.0f,
verticalStyle: FlowStyle = FlowStyle.Packed,
horizontalStyle: FlowStyle = FlowStyle.Packed,
): ConstrainedLayoutReference {
val ref = ConstrainedLayoutReference(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
if (it != null) {
elementArray.add(CLString.from(it.id.toString()))
}
}
val paddingArray = CLArray(charArrayOf()).apply {
add(CLNumber(paddingLeft.value))
add(CLNumber(paddingTop.value))
add(CLNumber(paddingRight.value))
add(CLNumber(paddingBottom.value))
}
ref.asCLContainer().apply {
put("contains", elementArray)
putString("type", if (flowVertically) "vFlow" else "hFlow")
putNumber("vGap", verticalGap.value)
putNumber("hGap", horizontalGap.value)
putNumber("maxElement", maxElement.toFloat())
put("padding", paddingArray)
putString("wrap", wrapMode.name)
putString("vAlign", verticalAlign.name)
putString("hAlign", horizontalAlign.name)
putNumber("hFlowBias", horizontalFlowBias)
putNumber("vFlowBias", verticalFlowBias)
putString("vStyle", verticalStyle.name)
putString("hStyle", horizontalStyle.name)
}
updateHelpersHashCode(16)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
return ref
}
/**
* Creates a Grid based helper that lays out its elements in a single Row.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val weights = intArrayOf(3, 3, 2, 2, 1)
* val g1 = createRow(
* a, b, c, d, e,
* spans = "1:2"
* skips = "1:1,3:2",
* horizontalGap = 10.dp,
* columnWeights = weights,
* padding = 10.dp,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param spans specify area(s) in a Row to be spanned - format: positionxsize
* @param skips specify area(s) in a Row to be skipped - format: positionxsize
* @param horizontalGap defines the gap between views in the x axis
* @param columnWeights defines the weight of each column
* @param padding sets padding around the content
*/
fun createRow(
vararg elements: LayoutReference,
spans: String = "",
skips: String = "",
horizontalGap: Dp = 0.dp,
columnWeights: IntArray = intArrayOf(),
padding: Dp = 0.dp,
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
rows = 1,
spans = spans,
skips = skips,
horizontalGap = horizontalGap,
columnWeights = columnWeights,
paddingLeft = padding,
paddingTop = padding,
paddingRight = padding,
paddingBottom = padding,
)
}
/**
* Creates a Grid based helper that lays out its elements in a single Row.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val weights = intArrayOf(3, 3, 2, 2, 1)
* val g1 = createRow(
* a, b, c, d, e,
* spans = "1:2"
* skips = "1:1,3:2",
* horizontalGap = 10.dp,
* columnWeights = weights,
* paddingHorizontal = 10.dp,
* paddingVertical = 10.dp,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param spans specify area(s) in a Row to be spanned - format: positionxsize
* @param skips specify area(s) in a Row to be skipped - format: positionxsize
* @param horizontalGap defines the gap between views in the y axis
* @param columnWeights defines the weight of each column
* @param paddingHorizontal sets paddingLeft and paddingRight of the content
* @param paddingVertical sets paddingTop and paddingBottom of the content
*/
fun createRow(
vararg elements: LayoutReference,
spans: String = "",
skips: String = "",
horizontalGap: Dp = 0.dp,
columnWeights: IntArray = intArrayOf(),
paddingHorizontal: Dp = 0.dp,
paddingVertical: Dp = 0.dp,
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
rows = 1,
spans = spans,
skips = skips,
horizontalGap = horizontalGap,
columnWeights = columnWeights,
paddingLeft = paddingHorizontal,
paddingTop = paddingVertical,
paddingRight = paddingHorizontal,
paddingBottom = paddingVertical,
)
}
/**
* Creates a Grid based helper that lays out its elements in a single Column.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val weights = intArrayOf(3, 3, 2, 2, 1)
* val g1 = createColumn(
* a, b, c, d, e,
* spans = "1:2"
* skips = "1:1,3:2",
* verticalGap = 10.dp,
* rowWeights = weights,
* padding = 10.dp,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param spans specify area(s) in a Column to be spanned - format: positionxsize
* @param skips specify area(s) in a Column to be skipped - format: positionxsize
* @param verticalGap defines the gap between views in the y axis
* @param rowWeights defines the weight of each row
* @param padding sets padding around the content
*/
fun createColumn(
vararg elements: LayoutReference,
spans: String = "",
skips: String = "",
rowWeights: IntArray = intArrayOf(),
verticalGap: Dp = 0.dp,
padding: Dp = 0.dp,
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
columns = 1,
spans = spans,
skips = skips,
verticalGap = verticalGap,
rowWeights = rowWeights,
paddingLeft = padding,
paddingTop = padding,
paddingRight = padding,
paddingBottom = padding,
)
}
/**
* Creates a Grid based helper that lays out its elements in a single Column.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val weights = intArrayOf(3, 3, 2, 2, 1)
* val g1 = createColumn(
* a, b, c, d, e,
* spans = "1:2"
* skips = "1:1,3:2",
* verticalGap = 10.dp,
* rowWeights = weights,
* padding = 10.dp,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param spans specify area(s) in a Column to be spanned - format: positionxsize
* @param skips specify area(s) in a Column to be skipped - format: positionxsize
* @param verticalGap defines the gap between views in the y axis
* @param rowWeights defines the weight of each row
* @param paddingHorizontal sets paddingLeft and paddingRight of the content
* @param paddingVertical sets paddingTop and paddingBottom of the content
*/
fun createColumn(
vararg elements: LayoutReference,
spans: String = "",
skips: String = "",
verticalGap: Dp = 0.dp,
rowWeights: IntArray = intArrayOf(),
paddingHorizontal: Dp = 0.dp,
paddingVertical: Dp = 0.dp,
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
columns = 1,
spans = spans,
skips = skips,
verticalGap = verticalGap,
rowWeights = rowWeights,
paddingLeft = paddingHorizontal,
paddingTop = paddingVertical,
paddingRight = paddingHorizontal,
paddingBottom = paddingVertical,
)
}
/**
* Creates a Grid representation with a Grid Helper.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val f = createRefFor("6")
* val g = createRefFor("7")
* val h = createRefFor("8")
* val i = createRefFor("9")
* val j = createRefFor("0")
* val k = createRefFor("box")
* val weights = intArrayOf(3, 3, 2, 2)
* val flags = arrayOf("SubGridByColRow", "SpansRespectWidgetOrder")
* val g1 = createGrid(
* k, a, b, c, d, e, f, g, h, i, j, k,
* rows = 5,
* columns = 3,
* verticalGap = 25.dp,
* horizontalGap = 25.dp,
* spans = "0:1x3",
* skips = "12:1x1",
* rowWeights = weights,
* paddingHorizontal = 10.dp,
* paddingVertical = 10.dp,
* flags = flags,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* Box(
* modifier = Modifier.background(Color.Gray).layoutId("box"),
* Alignment.BottomEnd
* ) {
* Text("100", fontSize = 80.sp)
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param orientation 0 if horizontal and 1 if vertical
* @param rows sets the number of rows in Grid
* @param columns sets the number of columns in Grid
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param rowWeights defines the weight of each row
* @param columnWeights defines the weight of each column
* @param skips defines the positions in a Grid to be skipped
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to skip
* col- the number of columns to skip
* @param spans defines the spanned area(s) in Grid
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to span
* col- the number of columns to span
* @param padding sets padding around the content
* @param flags set different flags to be enabled (not case-sensitive), including
* SubGridByColRow: reverse the width and height specification for spans/skips.
* Original - Position:HeightxWidth; with the flag - Position:WidthxHeight
* SpansRespectWidgetOrder: spans would respect the order of the widgets.
* Original - the widgets in the front of the widget list would be
* assigned to the spanned area; with the flag - all the widges will be arranged
* based on the given order. For example, for a layout with 1 row and 3 columns.
* If we have two widgets: w1, w2 with a span as 1:1x2, the original layout would
* be [w2 w1 w1]. Since w1 is in the front of the list, it would be assigned to
* the spanned area. With the flag, the layout would be [w1 w2 w2] that respects
* the order of the widget list.
*/
fun createGrid(
vararg elements: LayoutReference,
orientation: Int = 0,
rows: Int = 0,
columns: Int = 0,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
rowWeights: IntArray = intArrayOf(),
columnWeights: IntArray = intArrayOf(),
skips: String = "",
spans: String = "",
padding: Dp = 0.dp,
flags: Array<GridFlag> = arrayOf(),
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
orientation = orientation,
rows = rows,
columns = columns,
horizontalGap = horizontalGap,
verticalGap = verticalGap,
rowWeights = rowWeights,
columnWeights = columnWeights,
skips = skips,
spans = spans,
paddingLeft = padding,
paddingTop = padding,
paddingRight = padding,
paddingBottom = padding,
flags = flags,
)
}
/**
* Creates a Grid representation with a Grid Helper.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val f = createRefFor("6")
* val g = createRefFor("7")
* val h = createRefFor("8")
* val i = createRefFor("9")
* val j = createRefFor("0")
* val k = createRefFor("box")
* val weights = intArrayOf(3, 3, 2, 2)
* val flags = arrayOf("SubGridByColRow", "SpansRespectWidgetOrder")
* val g1 = createGrid(
* k, a, b, c, d, e, f, g, h, i, j, k,
* rows = 5,
* columns = 3,
* verticalGap = 25.dp,
* horizontalGap = 25.dp,
* spans = "0:1x3",
* skips = "12:1x1",
* rowWeights = weights,
* paddingHorizontal = 10.dp,
* paddingVertical = 10.dp,
* flags = flags,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* Box(
* modifier = Modifier.background(Color.Gray).layoutId("box"),
* Alignment.BottomEnd
* ) {
* Text("100", fontSize = 80.sp)
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param rowWeights defines the weight of each row
* @param rows sets the number of rows in Grid
* @param columns sets the number of columns in Grid
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param columnWeights defines the weight of each column
* @param orientation 0 if horizontal and 1 if vertical
* @param skips defines the positions in a Grid to be skipped
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to skip
* col- the number of columns to skip
* @param spans defines the spanned area(s) in Grid
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to span
* col- the number of columns to span
* @param paddingHorizontal sets paddingLeft and paddingRight of the content
* @param paddingVertical sets paddingTop and paddingBottom of the content
* @param flags set different flags to be enabled (not case-sensitive), including
* SubGridByColRow: reverse the width and height specification for spans/skips.
* Original - Position:HeightxWidth; with the flag - Position:WidthxHeight
* SpansRespectWidgetOrder: spans would respect the order of the widgets.
* Original - the widgets in the front of the widget list would be
* assigned to the spanned area; with the flag - all the widges will be arranged
* based on the given order. For example, for a layout with 1 row and 3 columns.
* If we have two widgets: w1, w2 with a span as 1:1x2, the original layout would
* be [w2 w1 w1]. Since w1 is in the front of the list, it would be assigned to
* the spanned area. With the flag, the layout would be [w1 w2 w2] that respects
* the order of the widget list.
*/
fun createGrid(
vararg elements: LayoutReference,
orientation: Int = 0,
rows: Int = 0,
columns: Int = 0,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
rowWeights: IntArray = intArrayOf(),
columnWeights: IntArray = intArrayOf(),
skips: String = "",
spans: String = "",
paddingHorizontal: Dp = 0.dp,
paddingVertical: Dp = 0.dp,
flags: Array<GridFlag> = arrayOf(),
): ConstrainedLayoutReference {
return createGrid(
elements = elements,
rowWeights = rowWeights,
columnWeights = columnWeights,
orientation = orientation,
rows = rows,
columns = columns,
horizontalGap = horizontalGap,
verticalGap = verticalGap,
skips = skips,
spans = spans,
paddingLeft = paddingHorizontal,
paddingTop = paddingVertical,
paddingRight = paddingHorizontal,
paddingBottom = paddingVertical,
flags = flags
)
}
/**
* Creates a Grid representation with a Grid Helper.
* Example:
* ConstraintLayout(
* ConstraintSet {
* val a = createRefFor("1")
* val b = createRefFor("2")
* val c = createRefFor("3")
* val d = createRefFor("4")
* val e = createRefFor("5")
* val f = createRefFor("6")
* val g = createRefFor("7")
* val h = createRefFor("8")
* val i = createRefFor("9")
* val j = createRefFor("0")
* val k = createRefFor("box")
* val weights = intArrayOf(3, 3, 2, 2)
* val flags = arrayOf("SubGridByColRow", "SpansRespectWidgetOrder")
* val g1 = createGrid(
* k, a, b, c, d, e, f, g, h, i, j, k,
* rows = 5,
* columns = 3,
* verticalGap = 25.dp,
* horizontalGap = 25.dp,
* spans = "0:1x3",
* skips = "12:1x1",
* rowWeights = weights,
* paddingLeft = 10.dp,
* paddingTop = 10.dp,
* paddingRight = 10.dp,
* paddingBottom = 10.dp,
* flags = flags,
* )
* constrain(g1) {
* width = Dimension.matchParent
* height = Dimension.matchParent
* },
* modifier = Modifier.fillMaxSize()
* ) {
* val numArray = arrayOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
* for (num in numArray) {
* Button(
* modifier = Modifier.layoutId(num).width(120.dp),
* onClick = {},
* ) {
* Text(text = String.format("btn%s", num))
* }
* }
* Box(
* modifier = Modifier.background(Color.Gray).layoutId("box"),
* Alignment.BottomEnd
* ) {
* Text("100", fontSize = 80.sp)
* }
* }
*
* @param elements [LayoutReference]s to be laid out by the Grid helper
* @param orientation 0 if horizontal and 1 if vertical
* @param rows sets the number of rows in Grid
* @param columns sets the number of columns in Grid
* @param verticalGap defines the gap between views in the y axis
* @param horizontalGap defines the gap between views in the x axis
* @param rowWeights defines the weight of each row
* @param columnWeights defines the weight of each column
* @param skips defines the positions in a Grid to be skipped
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to skip
* col- the number of columns to skip
* @param spans defines the spanned area(s) in Grid
* the format of the input string is "index:rowxcol"
* index - the index of the starting position
* row - the number of rows to span
* col- the number of columns to span
* @param paddingLeft sets paddingLeft of the content
* @param paddingTop sets paddingTop of the content
* @param paddingRight sets paddingRight of the content
* @param paddingBottom sets paddingBottom of the content
* @param flags set different flags to be enabled (not case-sensitive), including
* SubGridByColRow: reverse the width and height specification for spans/skips.
* Original - Position:HeightxWidth; with the flag - Position:WidthxHeight
* SpansRespectWidgetOrder: spans would respect the order of the widgets.
* Original - the widgets in the front of the widget list would be
* assigned to the spanned area; with the flag - all the widges will be arranged
* based on the given order. For example, for a layout with 1 row and 3 columns.
* If we have two widgets: w1, w2 with a span as 1:1x2, the original layout would
* be [w2 w1 w1]. Since w1 is in the front of the list, it would be assigned to
* the spanned area. With the flag, the layout would be [w1 w2 w2] that respects
* the order of the widget list.
*/
fun createGrid(
vararg elements: LayoutReference,
orientation: Int = 0,
rows: Int = 0,
columns: Int = 0,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
rowWeights: IntArray = intArrayOf(),
columnWeights: IntArray = intArrayOf(),
skips: String = "",
spans: String = "",
paddingLeft: Dp = 0.dp,
paddingTop: Dp = 0.dp,
paddingRight: Dp = 0.dp,
paddingBottom: Dp = 0.dp,
flags: Array<GridFlag> = arrayOf(),
): ConstrainedLayoutReference {
val ref = ConstrainedLayoutReference(createHelperId())
val elementArray = CLArray(charArrayOf())
val flagArray = CLArray(charArrayOf())
elements.forEach {
elementArray.add(CLString.from(it.id.toString()))
}
val paddingArray = CLArray(charArrayOf()).apply {
add(CLNumber(paddingLeft.value))
add(CLNumber(paddingTop.value))
add(CLNumber(paddingRight.value))
add(CLNumber(paddingBottom.value))
}
flags.forEach {
flagArray.add(CLString.from(it.name))
}
var strRowWeights = ""
var strColumnWeights = ""
if (rowWeights.size > 1) {
strRowWeights = rowWeights.joinToString(",")
}
if (columnWeights.size > 1) {
strColumnWeights = columnWeights.joinToString(",")
}
ref.asCLContainer().apply {
put("contains", elementArray)
putString("type", "grid")
putNumber("orientation", orientation.toFloat())
putNumber("rows", rows.toFloat())
putNumber("columns", columns.toFloat())
putNumber("vGap", verticalGap.value)
putNumber("hGap", horizontalGap.value)
put("padding", paddingArray)
putString("rowWeights", strRowWeights)
putString("columnWeights", strColumnWeights)
putString("skips", skips)
putString("spans", spans)
put("flags", flagArray)
}
return ref
}
/**
* Creates a horizontal chain including the referenced layouts.
*
* Use [constrain] with the resulting [HorizontalChainReference] to modify the start/left and
* end/right constraints of this chain.
*/
fun createHorizontalChain(
vararg elements: LayoutReference,
chainStyle: ChainStyle = ChainStyle.Spread
): HorizontalChainReference {
val ref = HorizontalChainReference(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
val chainParams = it.getHelperParams<ChainParams>()
val elementContent: CLElement = if (chainParams != null) {
CLArray(charArrayOf()).apply {
add(CLString.from(it.id.toString()))
add(CLNumber(chainParams.weight))
add(CLNumber(chainParams.startMargin.value))
add(CLNumber(chainParams.endMargin.value))
add(CLNumber(chainParams.startGoneMargin.value))
add(CLNumber(chainParams.endGoneMargin.value))
}
} else {
CLString.from(it.id.toString())
}
elementArray.add(elementContent)
}
val styleArray = CLArray(charArrayOf())
styleArray.add(CLString.from(chainStyle.name))
styleArray.add(CLNumber(chainStyle.bias ?: 0.5f))
ref.asCLContainer().apply {
putString("type", "hChain")
put("contains", elementArray)
put("style", styleArray)
}
updateHelpersHashCode(16)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(chainStyle.hashCode())
return ref
}
/**
* Creates a vertical chain including the referenced layouts.
*
* Use [constrain] with the resulting [VerticalChainReference] to modify the top and
* bottom constraints of this chain.
*/
fun createVerticalChain(
vararg elements: LayoutReference,
chainStyle: ChainStyle = ChainStyle.Spread
): VerticalChainReference {
val ref = VerticalChainReference(createHelperId())
val elementArray = CLArray(charArrayOf())
elements.forEach {
val chainParams = it.getHelperParams<ChainParams>()
val elementContent: CLElement = if (chainParams != null) {
CLArray(charArrayOf()).apply {
add(CLString.from(it.id.toString()))
add(CLNumber(chainParams.weight))
add(CLNumber(chainParams.topMargin.value))
add(CLNumber(chainParams.bottomMargin.value))
add(CLNumber(chainParams.topGoneMargin.value))
add(CLNumber(chainParams.bottomGoneMargin.value))
}
} else {
CLString.from(it.id.toString())
}
elementArray.add(elementContent)
}
val styleArray = CLArray(charArrayOf())
styleArray.add(CLString.from(chainStyle.name))
styleArray.add(CLNumber(chainStyle.bias ?: 0.5f))
ref.asCLContainer().apply {
putString("type", "vChain")
put("contains", elementArray)
put("style", styleArray)
}
updateHelpersHashCode(17)
elements.forEach { updateHelpersHashCode(it.hashCode()) }
updateHelpersHashCode(chainStyle.hashCode())
return ref
}
/**
* Sets the parameters that are used by chains to customize the resulting layout.
*
* Use margins to customize the space between widgets in the chain.
*
* Use weight to distribute available space to each widget when their dimensions are not
* fixed.
*
*
*
* Similarly named parameters available from [ConstrainScope.linkTo] are ignored in
* Chains.
*
* Since margins are only for widgets within the chain: Top, Start and End, Bottom margins are
* ignored when the widget is the first or the last element in the chain, respectively.
*
* @param startMargin Added space from the start of this widget to the previous widget
* @param topMargin Added space from the top of this widget to the previous widget
* @param endMargin Added space from the end of this widget to the next widget
* @param bottomMargin Added space from the bottom of this widget to the next widget
* @param startGoneMargin Added space from the start of this widget when the previous widget has [Visibility.Gone]
* @param topGoneMargin Added space from the top of this widget when the previous widget has [Visibility.Gone]
* @param endGoneMargin Added space from the end of this widget when the next widget has [Visibility.Gone]
* @param bottomGoneMargin Added space from the bottom of this widget when the next widget has [Visibility.Gone]
* @param weight Defines the proportion of space (relative to the total weight) occupied by this
* layout when the corresponding dimension is not a fixed value.
* @return The same [LayoutReference] instance with the applied values
*/
fun LayoutReference.withChainParams(
startMargin: Dp = 0.dp,
topMargin: Dp = 0.dp,
endMargin: Dp = 0.dp,
bottomMargin: Dp = 0.dp,
startGoneMargin: Dp = 0.dp,
topGoneMargin: Dp = 0.dp,
endGoneMargin: Dp = 0.dp,
bottomGoneMargin: Dp = 0.dp,
weight: Float = Float.NaN,
): LayoutReference =
this.apply {
setHelperParams(
ChainParams(
startMargin = startMargin,
topMargin = topMargin,
endMargin = endMargin,
bottomMargin = bottomMargin,
startGoneMargin = startGoneMargin,
topGoneMargin = topGoneMargin,
endGoneMargin = endGoneMargin,
bottomGoneMargin = bottomGoneMargin,
weight = weight
)
)
}
/**
* Sets the parameters that are used by horizontal chains to customize the resulting layout.
*
* Use margins to customize the space between widgets in the chain.
*
* Use weight to distribute available space to each widget when their horizontal dimension is
* not fixed.
*
*
*
* Similarly named parameters available from [ConstrainScope.linkTo] are ignored in
* Chains.
*
* Since margins are only for widgets within the chain: Start and End margins are
* ignored when the widget is the first or the last element in the chain, respectively.
*
* @param startMargin Added space from the start of this widget to the previous widget
* @param endMargin Added space from the end of this widget to the next widget
* @param startGoneMargin Added space from the start of this widget when the previous widget has [Visibility.Gone]
* @param endGoneMargin Added space from the end of this widget when the next widget has [Visibility.Gone]
* @param weight Defines the proportion of space (relative to the total weight) occupied by this
* layout when the width is not a fixed dimension.
* @return The same [LayoutReference] instance with the applied values
*/
fun LayoutReference.withHorizontalChainParams(
startMargin: Dp = 0.dp,
endMargin: Dp = 0.dp,
startGoneMargin: Dp = 0.dp,
endGoneMargin: Dp = 0.dp,
weight: Float = Float.NaN
): LayoutReference =
withChainParams(
startMargin = startMargin,
topMargin = 0.dp,
endMargin = endMargin,
bottomMargin = 0.dp,
startGoneMargin = startGoneMargin,
topGoneMargin = 0.dp,
endGoneMargin = endGoneMargin,
bottomGoneMargin = 0.dp,
weight = weight
)
/**
* Sets the parameters that are used by vertical chains to customize the resulting layout.
*
* Use margins to customize the space between widgets in the chain.
*
* Use weight to distribute available space to each widget when their vertical dimension is not
* fixed.
*
*
*
* Similarly named parameters available from [ConstrainScope.linkTo] are ignored in
* Chains.
*
* Since margins are only for widgets within the chain: Top and Bottom margins are
* ignored when the widget is the first or the last element in the chain, respectively.
*
* @param topMargin Added space from the top of this widget to the previous widget
* @param bottomMargin Added space from the bottom of this widget to the next widget
* @param topGoneMargin Added space from the top of this widget when the previous widget has [Visibility.Gone]
* @param bottomGoneMargin Added space from the bottom of this widget when the next widget has [Visibility.Gone]
* @param weight Defines the proportion of space (relative to the total weight) occupied by this
* layout when the height is not a fixed dimension.
* @return The same [LayoutReference] instance with the applied values
*/
fun LayoutReference.withVerticalChainParams(
topMargin: Dp = 0.dp,
bottomMargin: Dp = 0.dp,
topGoneMargin: Dp = 0.dp,
bottomGoneMargin: Dp = 0.dp,
weight: Float = Float.NaN
): LayoutReference =
withChainParams(
startMargin = 0.dp,
topMargin = topMargin,
endMargin = 0.dp,
bottomMargin = bottomMargin,
startGoneMargin = 0.dp,
topGoneMargin = topGoneMargin,
endGoneMargin = 0.dp,
bottomGoneMargin = bottomGoneMargin,
weight = weight
)
internal fun LayoutReference.asCLContainer(): CLObject {
val idString = id.toString()
if (containerObject.getObjectOrNull(idString) == null) {
containerObject.put(idString, CLObject(charArrayOf()))
}
return containerObject.getObject(idString)
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other is ConstraintLayoutBaseScope) {
return containerObject == other.containerObject
}
return false
}
override fun hashCode(): Int {
return containerObject.hashCode()
}
}
/**
* Represents a [ConstraintLayout] item that requires a unique identifier. Typically a layout or a
* helper such as barriers, guidelines or chains.
*/
@Stable
abstract class LayoutReference internal constructor(internal open val id: Any) {
/**
* This map should be used to store one instance of different implementations of [HelperParams].
*/
private val helperParamsMap: MutableMap<String, HelperParams> = mutableMapOf()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LayoutReference
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
internal fun setHelperParams(helperParams: HelperParams) {
// Use the class name to force one instance per implementation
helperParamsMap[helperParams.javaClass.simpleName] = helperParams
}
/**
* Returns the [HelperParams] that corresponds to the class type [T]. Null if no instance of
* type [T] has been set.
*/
internal inline fun <reified T> getHelperParams(): T? where T : HelperParams {
return helperParamsMap[T::class.java.simpleName] as? T
}
}
/**
* Helpers that need parameters on a per-widget basis may implement this interface to store custom
* parameters within [LayoutReference].
*
* @see [LayoutReference.getHelperParams]
* @see [LayoutReference.setHelperParams]
*/
internal interface HelperParams
/**
* Parameters that may be defined for each widget within a chain.
*
* These will always be used instead of similarly named parameters defined with other calls such as
* [ConstrainScope.linkTo].
*/
internal class ChainParams(
val startMargin: Dp,
val topMargin: Dp,
val endMargin: Dp,
val bottomMargin: Dp,
val startGoneMargin: Dp,
val topGoneMargin: Dp,
val endGoneMargin: Dp,
val bottomGoneMargin: Dp,
val weight: Float,
) : HelperParams {
companion object {
internal val Default = ChainParams(
startMargin = 0.dp,
topMargin = 0.dp,
endMargin = 0.dp,
bottomMargin = 0.dp,
startGoneMargin = 0.dp,
topGoneMargin = 0.dp,
endGoneMargin = 0.dp,
bottomGoneMargin = 0.dp,
weight = Float.NaN
)
}
}
/**
* Basic implementation of [LayoutReference], used as fallback for items that don't fit other
* implementations of [LayoutReference], such as [ConstrainedLayoutReference].
*/
@Stable
internal class LayoutReferenceImpl internal constructor(id: Any) : LayoutReference(id)
/**
* Represents a layout within a [ConstraintLayout].
*
* This is a [LayoutReference] that may be constrained to other elements.
*/
@Stable
class ConstrainedLayoutReference(override val id: Any) : LayoutReference(id) {
/**
* The start anchor of this layout. Represents left in LTR layout direction, or right in RTL.
*/
@Stable
val start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2, this)
/**
* The left anchor of this layout.
*/
@Stable
val absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0, this)
/**
* The top anchor of this layout.
*/
@Stable
val top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0, this)
/**
* The end anchor of this layout. Represents right in LTR layout direction, or left in RTL.
*/
@Stable
val end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1, this)
/**
* The right anchor of this layout.
*/
@Stable
val absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1, this)
/**
* The bottom anchor of this layout.
*/
@Stable
val bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1, this)
/**
* The baseline anchor of this layout.
*/
@Stable
val baseline = ConstraintLayoutBaseScope.BaselineAnchor(id, this)
}
/**
* Represents a horizontal chain within a [ConstraintLayout].
*
* The anchors correspond to the first and last elements in the chain.
*/
@Stable
class HorizontalChainReference internal constructor(id: Any) : LayoutReference(id) {
/**
* The start anchor of the first element in the chain.
*
* Represents left in LTR layout direction, or right in RTL.
*/
@Stable
val start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2, this)
/**
* The left anchor of the first element in the chain.
*/
@Stable
val absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0, this)
/**
* The end anchor of the last element in the chain.
*
* Represents right in LTR layout direction, or left in RTL.
*/
@Stable
val end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1, this)
/**
* The right anchor of the last element in the chain.
*/
@Stable
val absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1, this)
}
/**
* Represents a vertical chain within a [ConstraintLayout].
*
* The anchors correspond to the first and last elements in the chain.
*/
@Stable
class VerticalChainReference internal constructor(id: Any) : LayoutReference(id) {
/**
* The top anchor of the first element in the chain.
*/
@Stable
val top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0, this)
/**
* The bottom anchor of the last element in the chain.
*/
@Stable
val bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1, this)
}
/**
* The style of a horizontal or vertical chain.
*/
@Immutable
class ChainStyle internal constructor(
internal val name: String,
internal val bias: Float? = null
) {
companion object {
/**
* A chain style that evenly distributes the contained layouts.
*/
@Stable
val Spread = ChainStyle("spread")
/**
* A chain style where the first and last layouts are affixed to the constraints
* on each end of the chain and the rest are evenly distributed.
*/
@Stable
val SpreadInside = ChainStyle("spread_inside")
/**
* A chain style where the contained layouts are packed together and placed to the
* center of the available space.
*/
@Stable
val Packed = Packed(0.5f)
/**
* A chain style where the contained layouts are packed together and placed in
* the available space according to a given [bias].
*/
@Stable
fun Packed(bias: Float) = ChainStyle("packed", bias)
}
}
/**
* The overall visibility of a widget in a [ConstraintLayout].
*/
@Immutable
class Visibility internal constructor(
internal val name: String
) {
companion object {
/**
* Indicates that the widget will be painted in the [ConstraintLayout]. All render-time
* transforms will apply normally.
*/
@Stable
val Visible = Visibility("visible")
/**
* The widget will not be painted in the [ConstraintLayout] but its dimensions and constraints
* will still apply.
*
* Equivalent to forcing the alpha to 0.0.
*/
@Stable
val Invisible = Visibility("invisible")
/**
* Like [Invisible], but the dimensions of the widget will collapse to (0,0), the
* constraints will still apply.
*/
@Stable
val Gone = Visibility("gone")
}
}
/**
* GridFlag defines the available flags of Grid
* SubGridByColRow: reverse the width and height specification for spans/skips.
* Original - Position:HeightxWidth; with the flag - Position:WidthxHeight
* SpansRespectWidgetOrder: spans would respect the order of the widgets.
* Original - the widgets in the front of the widget list would be
* assigned to the spanned area; with the flag - all the widges will be arranged
* based on the given order. For example, for a layout with 1 row and 3 columns.
* If we have two widgets: w1, w2 with a span as 1:1x2, the original layout would
* be [w2 w1 w1]. Since w1 is in the front of the list, it would be assigned to
* the spanned area. With the flag, the layout would be [w1 w2 w2] that respects
* the order of the widget list.
*/
@Immutable
class GridFlag internal constructor(
internal val name: String
) {
companion object {
val SpansRespectWidgetOrder = GridFlag("spansrespectwidgetorder")
val SubGridByColRow = GridFlag("subgridbycolrow")
}
}
/**
* Wrap defines the type of chain
*/
@Immutable
class Wrap internal constructor(
internal val name: String
) {
companion object {
val None = Wrap("none")
val Chain = Wrap("chain")
val Aligned = Wrap("aligned")
}
}
/**
* Defines how objects align vertically within the chain
*/
@Immutable
class VerticalAlign internal constructor(
internal val name: String
) {
companion object {
val Top = VerticalAlign("top")
val Bottom = VerticalAlign("bottom")
val Center = VerticalAlign("center")
val Baseline = VerticalAlign("baseline")
}
}
/**
* Defines how objects align horizontally in the chain
*/
@Immutable
class HorizontalAlign internal constructor(
internal val name: String
) {
companion object {
val Start = HorizontalAlign("start")
val End = HorizontalAlign("end")
val Center = HorizontalAlign("center")
}
}
/**
* Defines how widgets are spaced in a chain
*/
@Immutable
class FlowStyle internal constructor(
internal val name: String
) {
companion object {
val Spread = FlowStyle("spread")
val SpreadInside = FlowStyle("spread_inside")
val Packed = FlowStyle("packed")
}
}