![JAR search and dependency download from the Maven repository](/logo.png)
commonMain.androidx.compose.ui.layout.LayoutModifier.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ui Show documentation
Show all versions of ui Show documentation
Compose UI primitives. This library contains the primitives that form the Compose UI Toolkit, such as drawing, measurement and layout.
/*
* Copyright 2020 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.compose.ui.layout
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.internal.JvmDefaultWithCompatibility
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
/**
* A [Modifier.Element] that changes how its wrapped content is measured and laid out.
* It has the same measurement and layout functionality as the [androidx.compose.ui.layout.Layout]
* component, while wrapping exactly one layout due to it being a modifier. In contrast,
* the [androidx.compose.ui.layout.Layout] component is used to define the layout behavior of
* multiple children.
*
* @sample androidx.compose.ui.samples.LayoutModifierSample
*
* @see androidx.compose.ui.layout.Layout
*/
@JvmDefaultWithCompatibility
interface LayoutModifier : Modifier.Element {
/**
* The function used to measure the modifier. The [measurable] corresponds to the
* wrapped content, and it can be measured with the desired constraints according
* to the logic of the [LayoutModifier]. The modifier needs to choose its own
* size, which can depend on the size chosen by the wrapped content (the obtained
* [Placeable]), if the wrapped content was measured. The size needs to be returned
* as part of a [MeasureResult], alongside the placement logic of the
* [Placeable], which defines how the wrapped content should be positioned inside
* the [LayoutModifier]. A convenient way to create the [MeasureResult]
* is to use the [MeasureScope.layout] factory function.
*
* A [LayoutModifier] uses the same measurement and layout concepts and principles as a
* [Layout], the only difference is that they apply to exactly one child. For a more detailed
* explanation of measurement and layout, see [MeasurePolicy].
*/
fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
/**
* The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth].
*/
fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = MeasuringIntrinsics.minWidth(
this@LayoutModifier,
this,
measurable,
height
)
/**
* The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight].
*/
fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
): Int = MeasuringIntrinsics.minHeight(
this@LayoutModifier,
this,
measurable,
width
)
/**
* The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth].
*/
fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = MeasuringIntrinsics.maxWidth(
this@LayoutModifier,
this,
measurable,
height
)
/**
* The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight].
*/
fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
): Int = MeasuringIntrinsics.maxHeight(
this@LayoutModifier,
this,
measurable,
width
)
}
// TODO(popam): deduplicate from the copy-pasted logic of Layout.kt without making it public
private object MeasuringIntrinsics {
fun minWidth(
modifier: LayoutModifier,
intrinsicMeasureScope: IntrinsicMeasureScope,
intrinsicMeasurable: IntrinsicMeasurable,
h: Int
): Int {
val measurable = DefaultIntrinsicMeasurable(
intrinsicMeasurable,
IntrinsicMinMax.Min,
IntrinsicWidthHeight.Width
)
val constraints = Constraints(maxHeight = h)
val layoutResult = with(modifier) {
IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
.measure(measurable, constraints)
}
return layoutResult.width
}
fun minHeight(
modifier: LayoutModifier,
intrinsicMeasureScope: IntrinsicMeasureScope,
intrinsicMeasurable: IntrinsicMeasurable,
w: Int
): Int {
val measurable = DefaultIntrinsicMeasurable(
intrinsicMeasurable,
IntrinsicMinMax.Min,
IntrinsicWidthHeight.Height
)
val constraints = Constraints(maxWidth = w)
val layoutResult = with(modifier) {
IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
.measure(measurable, constraints)
}
return layoutResult.height
}
fun maxWidth(
modifier: LayoutModifier,
intrinsicMeasureScope: IntrinsicMeasureScope,
intrinsicMeasurable: IntrinsicMeasurable,
h: Int
): Int {
val measurable = DefaultIntrinsicMeasurable(
intrinsicMeasurable,
IntrinsicMinMax.Max,
IntrinsicWidthHeight.Width
)
val constraints = Constraints(maxHeight = h)
val layoutResult = with(modifier) {
IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
.measure(measurable, constraints)
}
return layoutResult.width
}
fun maxHeight(
modifier: LayoutModifier,
intrinsicMeasureScope: IntrinsicMeasureScope,
intrinsicMeasurable: IntrinsicMeasurable,
w: Int
): Int {
val measurable = DefaultIntrinsicMeasurable(
intrinsicMeasurable,
IntrinsicMinMax.Max,
IntrinsicWidthHeight.Height
)
val constraints = Constraints(maxWidth = w)
val layoutResult = with(modifier) {
IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
.measure(measurable, constraints)
}
return layoutResult.height
}
private class DefaultIntrinsicMeasurable(
val measurable: IntrinsicMeasurable,
val minMax: IntrinsicMinMax,
val widthHeight: IntrinsicWidthHeight
) : Measurable {
override val parentData: Any?
get() = measurable.parentData
override fun measure(constraints: Constraints): Placeable {
if (widthHeight == IntrinsicWidthHeight.Width) {
val width = if (minMax == IntrinsicMinMax.Max) {
measurable.maxIntrinsicWidth(constraints.maxHeight)
} else {
measurable.minIntrinsicWidth(constraints.maxHeight)
}
val height =
if (constraints.hasBoundedHeight) constraints.maxHeight else LargeDimension
return EmptyPlaceable(width, height)
}
val height = if (minMax == IntrinsicMinMax.Max) {
measurable.maxIntrinsicHeight(constraints.maxWidth)
} else {
measurable.minIntrinsicHeight(constraints.maxWidth)
}
val width = if (constraints.hasBoundedWidth) constraints.maxWidth else LargeDimension
return EmptyPlaceable(width, height)
}
override fun minIntrinsicWidth(height: Int): Int {
return measurable.minIntrinsicWidth(height)
}
override fun maxIntrinsicWidth(height: Int): Int {
return measurable.maxIntrinsicWidth(height)
}
override fun minIntrinsicHeight(width: Int): Int {
return measurable.minIntrinsicHeight(width)
}
override fun maxIntrinsicHeight(width: Int): Int {
return measurable.maxIntrinsicHeight(width)
}
}
private class EmptyPlaceable(width: Int, height: Int) : Placeable() {
init {
measuredSize = IntSize(width, height)
}
override fun get(alignmentLine: AlignmentLine): Int = AlignmentLine.Unspecified
override fun placeAt(
position: IntOffset,
zIndex: Float,
layerBlock: (GraphicsLayerScope.() -> Unit)?
) {
}
}
private enum class IntrinsicMinMax { Min, Max }
private enum class IntrinsicWidthHeight { Width, Height }
}
/**
* Creates a [LayoutModifier] that allows changing how the wrapped element is measured and laid out.
*
* This is a convenience API of creating a custom [LayoutModifier] modifier, without having to
* create a class or an object that implements the [LayoutModifier] interface. The intrinsic
* measurements follow the default logic provided by the [LayoutModifier].
*
* Example usage:
*
* @sample androidx.compose.ui.samples.ConvenienceLayoutModifierSample
*
* @see androidx.compose.ui.layout.LayoutModifier
*/
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutElement(measure)
private data class LayoutElement(
val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : ModifierNodeElement() {
override fun create() = LayoutModifierImpl(measure)
override fun update(node: LayoutModifierImpl) {
node.measureBlock = measure
}
override fun InspectorInfo.inspectableProperties() {
name = "layout"
properties["measure"] = measure
}
}
internal class LayoutModifierImpl(
var measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : LayoutModifierNode, Modifier.Node() {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
) = measureBlock(measurable, constraints)
override fun toString(): String {
return "LayoutModifierImpl(measureBlock=$measureBlock)"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy