graph TB
XxModifier-->MeasureScope.measure("MeasureScope.measure")-->|1|measurable("measurable.measure(constraints)")
measurable-->|1|performMeasure
performMeasure-->|1|MeasureScope.measure
MeasureScope.measure-->|2|MeasureScope.layout
graph TB
InnerPlaceable.performDraw-->|forEach|drawChild("child.draw(canvas)")-->ViewLayer.drawLayer
-->ViewLayer.dispatchDraw-->|drawBlock|LayoutNodeWrapper.invoke-->performDraw("performDraw(canvas)")
-->|leafNode?|DrawModifier.ContentDrawScope.draw
performDraw-->|parentNode?|InnerPlaceable.performDraw
classDiagram
class IntrinsicMeasurable{
<<interface>>
+parentData: Any?
}
class Measurable {
<<interface>>
+measure(constraints: Constraints): Placeable
}
IntrinsicMeasurable<|--Measurable
Measurable<|--LayoutNode
Measurable<|--OuterMeasurablePlaceable
Measurable<|--LayoutNodeWrapper
class Measured {
<<interface>>
+measuredWidth: Int
+measuredHeight: Int
}
class Placeable {
+place(x: Int, y: Int, zIndex: Float = 0f)
}
class LayoutNodeWrapper{
+layoutNode: LayoutNode
+layer: OwnedLayer
+performMeasure(constraints: Constraints)Placeable
+draw(canvas: Canvas)
}
class OuterMeasurablePlaceable {
+layoutNode: LayoutNode
+outerWrapper: LayoutNodeWrapper
}
class LayoutNode {
+innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
+outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)
+outerLayoutNodeWrapper: LayoutNodeWrapper
+mDrawScope: LayoutNodeDrawScope = sharedDrawScope
}
class ModifiedDrawNode{
+modifier: DrawModifier
}
class DelegatingLayoutNodeWrapper~T : Modifier.Element~ {
+wrapped: LayoutNodeWrapper
+modifier: T
}
Measured<|--Placeable
Placeable<|--OuterMeasurablePlaceable
Placeable<|--LayoutNodeWrapper
LayoutNodeWrapper<|--InnerPlaceable
LayoutNodeWrapper<|--DelegatingLayoutNodeWrapper
DelegatingLayoutNodeWrapper<|--ModifiedLayoutNode
DelegatingLayoutNodeWrapper<|--ModifiedDrawNode
classDiagram
Modifier<|--Element
class LayoutModifier {
+MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
}
class PaddingModifier {
+MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
}
class OffsetModifier {
+MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
}
Element<|--LayoutModifier
LayoutModifier<|--PaddingModifier
LayoutModifier<|--OffsetModifier
class IntrinsicMeasureScope {
+layoutDirection: LayoutDirection
}
Density<|--IntrinsicMeasureScope
class MeasureScope {
+layout() MeasureResult
}
IntrinsicMeasureScope<|--MeasureScope
class DrawModifier {
+ContentDrawScope.draw()
}
class Background {
+ContentDrawScope.draw()
}
class PainterModifier {
+ContentDrawScope.draw()
}
Element<|--DrawModifier
LayoutModifier<|--PainterModifier
DrawModifier<|--PainterModifier
DrawModifier<|--Background
class DrawScope {
<<interface>>
+drawContext: DrawContext
+layoutDirection: LayoutDirection
}
class ContentDrawScope {
+drawContent()
}
class LayoutNodeDrawScope {
+canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
}
class CanvasDrawScope {
+draw(...)
}
Density<|--DrawScope
DrawScope<|--ContentDrawScope
ContentDrawScope<|--LayoutNodeDrawScope
DrawScope<|--CanvasDrawScope
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a first baseline
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// Height of the composable with padding - first baseline
val placeableY = firstBaselineToTop.toIntPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// Where the composable gets placed
placeable.place(0, placeableY)
}
}
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable() () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints.copy(minHeight = 0))
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.place(x = 0, y = yPosition)
// Record the y co-ord placed up to
yPosition += placeable.height
}
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
trace("AndroidOwner:onMeasure") {
if (!isAttachedToWindow) {
invalidateLayoutNodeMeasurement(root)
}
val (minWidth, maxWidth) = convertMeasureSpec(widthMeasureSpec)
val (minHeight, maxHeight) = convertMeasureSpec(heightMeasureSpec)
val constraints = Constraints(minWidth, maxWidth, minHeight, maxHeight)
if (onMeasureConstraints == null) {
// first onMeasure after last onLayout
onMeasureConstraints = constraints
wasMeasuredWithMultipleConstraints = false
} else if (onMeasureConstraints != constraints) {
// we were remeasured twice with different constraints after last onLayout
wasMeasuredWithMultipleConstraints = true
}
measureAndLayoutDelegate.updateRootConstraints(constraints)
measureAndLayoutDelegate.measureAndLayout()//main
setMeasuredDimension(root.width, root.height)//main
}
}
private fun convertMeasureSpec(measureSpec: Int): Pair<Int, Int> {
val mode = MeasureSpec.getMode(measureSpec)
val size = MeasureSpec.getSize(measureSpec)
return when (mode) {
MeasureSpec.EXACTLY -> size to size
MeasureSpec.UNSPECIFIED -> 0 to Constraints.Infinity
MeasureSpec.AT_MOST -> 0 to size
else -> throw IllegalStateException()
}
}
/**
* @param constraints The constraints to measure the root [LayoutNode] with
*/
fun updateRootConstraints(constraints: Constraints) {
if (rootConstraints != constraints) {
rootConstraints = constraints
root.layoutState = NeedsRemeasure
relayoutNodes.add(root)
}
}
override val root = LayoutNode().also {
it.measurePolicy = RootMeasurePolicy
it.modifier = Modifier
.then(semanticsModifier)
.then(_focusManager.modifier)
.then(keyInputModifier)
}
/**
* Iterates through all LayoutNodes that have requested layout and measures and lays them out
*/
fun measureAndLayout(): Boolean {
....
if (relayoutNodes.isNotEmpty()) {
relayoutNodes.popEach { layoutNode ->
if (layoutNode.layoutState == NeedsRemeasure) {
if (doRemeasure(layoutNode, rootConstraints)) {
rootNodeResized = true
}
}
if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
if (layoutNode === root) {
layoutNode.place(0, 0)
} else {
layoutNode.replace()
}
onPositionedDispatcher.onNodePositioned(layoutNode)
}
measureIteration++
// execute postponed `onRequestMeasure`
if (postponedMeasureRequests.isNotEmpty()) {
postponedMeasureRequests.fastForEach {
if (it.isAttached) {
requestRemeasure(it)
}
}
postponedMeasureRequests.clear()
}
}
/** MeasureAndLayoutDelegate.kt
* Requests remeasure for this [layoutNode] and nodes affected by its measure result.
*
* @return returns true if the [measureAndLayout] execution should be scheduled as a result
* of the request.
*/
fun requestRemeasure(layoutNode: LayoutNode): Boolean = when (layoutNode.layoutState) {
Measuring, NeedsRemeasure -> {
// requestMeasure has already been called for this node or
// we're currently measuring it, let's swallow. example when it happens: we compose
// DataNode inside BoxWithConstraints, this calls onRequestMeasure on DataNode's
// parent, but this parent is BoxWithConstraints which is currently measuring.
false
}
LayingOut -> {
// requestMeasure is currently laying out and it is incorrect to request remeasure
// now, let's postpone it.
postponedMeasureRequests.add(layoutNode)
consistencyChecker?.assertConsistent()
false
}
NeedsRelayout, Ready -> {
if (duringMeasureLayout && layoutNode.wasMeasuredDuringThisIteration) {
postponedMeasureRequests.add(layoutNode)
} else {
layoutNode.layoutState = NeedsRemeasure
if (layoutNode.isPlaced || layoutNode.canAffectParent) {
val parentLayoutState = layoutNode.parent?.layoutState
if (parentLayoutState != NeedsRemeasure) {
relayoutNodes.add(layoutNode)
}
}
}
!duringMeasureLayout
}
}
private fun doRemeasure(layoutNode: LayoutNode, rootConstraints: Constraints): Boolean {
val sizeChanged = if (layoutNode === root) {
layoutNode.remeasure(rootConstraints)
} else {
layoutNode.remeasure()
}
val parent = layoutNode.parent
if (sizeChanged) {
if (parent == null) {
return true
} else if (layoutNode.measuredByParent == InMeasureBlock) {
requestRemeasure(parent)
} else {
require(layoutNode.measuredByParent == InLayoutBlock)
requestRelayout(parent)
}
}
return false
}
/**
* Return true if the measured size has been changed
*/
internal fun remeasure(
constraints: Constraints = outerMeasurablePlaceable.lastConstraints!!
) = outerMeasurablePlaceable.remeasure(constraints)
/** OuterMeasurablePlaceable.kt
* Return true if the measured size has been changed
*/
fun remeasure(constraints: Constraints): Boolean {
val outerWrapperPreviousMeasuredSize = outerWrapper.size
owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
outerWrapper.measure(constraints)//main
}
}
/** LayoutNodeWrapper.kt
* Measures the modified child.
*/
final override fun measure(constraints: Constraints): Placeable {
measurementConstraints = constraints
val result = performMeasure(constraints)
layer?.resize(measuredSize)
return result
}
internal class ModifiedLayoutNode(
wrapped: LayoutNodeWrapper,
modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {
override fun performMeasure(constraints: Constraints): Placeable = with(modifier) {
measureResult = measureScope.measure(wrapped, constraints)
this@ModifiedLayoutNode
}
//Padding.kt
@Stable
fun Modifier.padding(all: Dp) =
this.then(
PaddingModifier(
start = all,
top = all,
end = all,
bottom = all,
rtlAware = true,
inspectorInfo = debugInspectorInfo {
name = "padding"
value = all
}
)
)
private class PaddingModifier(
val start: Dp = 0.dp,
val top: Dp = 0.dp,
val end: Dp = 0.dp,
val bottom: Dp = 0.dp,
val rtlAware: Boolean,
inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
val horizontal = start.roundToPx() + end.roundToPx()
val vertical = top.roundToPx() + bottom.roundToPx()
val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
val width = constraints.constrainWidth(placeable.width + horizontal)
val height = constraints.constrainHeight(placeable.height + vertical)
return layout(width, height) {
if (rtlAware) {
placeable.placeRelative(start.roundToPx(), top.roundToPx())
} else {
placeable.place(start.roundToPx(), top.roundToPx())
}
}
}
@Composable
fun Spacer(modifier: Modifier) {
Layout({}, modifier) { _, constraints ->
with(constraints) {
val width = if (hasFixedWidth) maxWidth else 0
val height = if (hasFixedHeight) maxHeight else 0
layout(width, height) {}
}
}
}
override fun placeAt(
position: IntOffset,
zIndex: Float,
layerBlock: (GraphicsLayerScope.() -> Unit)?
) {
onLayerBlockUpdated(layerBlock)
if (this.position != position) {
this.position = position
val layer = layer
if (layer != null) {
layer.move(position)
} else {
wrappedBy?.invalidateLayer()
}
layoutNode.owner?.onLayoutChange(layoutNode)
}
this.zIndex = zIndex
}
fun onLayerBlockUpdated(layerBlock: (GraphicsLayerScope.() -> Unit)?) {
val blockHasBeenChanged = this.layerBlock !== layerBlock
this.layerBlock = layerBlock
if (isAttached && layerBlock != null) {
if (layer == null) {
layer = layoutNode.requireOwner().createLayer(
this,
invalidateParentLayer
override fun createLayer(
drawBlock: (Canvas) -> Unit,
invalidateParentLayer: () -> Unit
): OwnedLayer {
// RenderNode is supported on Q+ for certain, but may also be supported on M-O.
// We can't be confident that RenderNode is supported, so we try and fail over to
// the ViewLayer implementation. We'll try even on on P devices, but it will fail
// until ART allows things on the unsupported list on P.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRenderNodeCompatible) {
try {
return RenderNodeLayer(
this,
drawBlock,
invalidateParentLayer
)
} catch (_: Throwable) {
isRenderNodeCompatible = false
}
}
return ViewLayer(
this,
viewLayersContainer,
drawBlock,
invalidateParentLayer
)
}
internal class ViewLayer(
val ownerView: AndroidComposeView,
val container: ViewLayerContainer,
val drawBlock: (Canvas) -> Unit,
val invalidateParentLayer: () -> Unit
) : View(ownerView.context), OwnedLayer {
init {
setWillNotDraw(false) // we WILL draw
id = generateViewId()
container.addView(this)
}
}
override fun dispatchDraw(canvas: android.graphics.Canvas) {
if (!isAttachedToWindow) {
invalidateLayers(root)
}
measureAndLayout()
// we don't have to observe here because the root has a layer modifier
// that will observe all children. The AndroidComposeView has only the
// root, so it doesn't have to invalidate itself based on model changes.
canvasHolder.drawInto(canvas) { root.draw(this) }//main
if (dirtyLayers.isNotEmpty()) {
for (i in 0 until dirtyLayers.size) {
val layer = dirtyLayers[i]
layer.updateDisplayList()
}
dirtyLayers.clear()
}
}
internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
/**
* Draws the content of the LayoutNode
*/
fun draw(canvas: Canvas) {
val layer = layer
if (layer != null) {
layer.drawLayer(canvas)//main
} else {
val x = position.x.toFloat()
val y = position.y.toFloat()
canvas.translate(x, y)
performDraw(canvas)//main
canvas.translate(-x, -y)
}
}
override fun performDraw(canvas: Canvas) {
wrapped.draw(canvas)
}
override fun drawLayer(canvas: Canvas) {
drawnWithZ = elevation > 0f
if (drawnWithZ) {
canvas.enableZ()
}
container.drawChild(canvas, this, drawingTime)//main
if (drawnWithZ) {
canvas.disableZ()
}
}
// we change visibility for this method so ViewLayer can use it for drawing
internal fun drawChild(canvas: Canvas, view: View, drawingTime: Long) {
super.drawChild(canvas.nativeCanvas, view, drawingTime)//call ViewGroup's drawChild
}
override fun dispatchDraw(canvas: android.graphics.Canvas) {
canvasHolder.drawInto(canvas) {
val clipPath = manualClipPath
if (clipPath != null) {
save()
clipPath(clipPath)
}
drawBlock(this)//main
if (clipPath != null) {
restore()
}
isInvalidated = false
}
}
/**
* Holder class that is used to issue scoped calls to a [Canvas] from the framework
* equivalent canvas without having to allocate an object on each draw call
*/
class CanvasHolder {
@PublishedApi internal val androidCanvas = AndroidCanvas()
inline fun drawInto(targetCanvas: android.graphics.Canvas, block: Canvas.() -> Unit) {
val previousCanvas = androidCanvas.internalCanvas
androidCanvas.internalCanvas = targetCanvas
androidCanvas.block()
androidCanvas.internalCanvas = previousCanvas
}
}
// implementation of draw block passed to the OwnedLayer
override fun invoke(canvas: Canvas) {
if (layoutNode.isPlaced) {
require(layoutNode.layoutState == LayoutNode.LayoutState.Ready) {
"Layer is redrawn for LayoutNode in state ${layoutNode.layoutState} [$layoutNode]"
}
snapshotObserver.observeReads(this, onCommitAffectingLayer) {
performDraw(canvas)//main
}
lastLayerDrawingWasSkipped = false
} else {
// The invalidation is requested even for nodes which are not placed. As we are not
// going to display them we skip the drawing. It is safe to just draw nothing as the
// layer will be invalidated again when the node will be finally placed.
lastLayerDrawingWasSkipped = true
}
}
override fun performDraw(canvas: Canvas) {
val owner = layoutNode.requireOwner()//AndroidComposeView
layoutNode.zSortedChildren.forEach { child ->
if (child.isPlaced) {
require(child.layoutState == LayoutNode.LayoutState.Ready) {
"$child is not ready. layoutState is ${child.layoutState}"
}
child.draw(canvas)//main
}
}
if (owner.showLayoutBounds) {
drawBorder(canvas, innerBoundsPaint)
}
}
internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
internal val outerLayoutNodeWrapper: LayoutNodeWrapper
get() = outerMeasurablePlaceable.outerWrapper
override fun performDraw(canvas: Canvas) {
val size = measuredSize.toSize()
if (cacheDrawModifier != null && invalidateCache) {
layoutNode.requireOwner().snapshotObserver.observeReads(
this,
onCommitAffectingModifiedDrawNode,
updateCache
)
}
val drawScope = layoutNode.mDrawScope
drawScope.draw(canvas, size, wrapped) {
with(drawScope) {
with(modifier) {
draw()
}
}
}
}
internal inline fun draw(
canvas: Canvas,
size: Size,
LayoutNodeWrapper: LayoutNodeWrapper,
block: DrawScope.() -> Unit
) {
val previousWrapper = wrapped
wrapped = LayoutNodeWrapper
canvasDrawScope.draw(
LayoutNodeWrapper.measureScope,
LayoutNodeWrapper.measureScope.layoutDirection,
canvas,
size,
block
)
wrapped = previousWrapper
}
/**
* Draws into the provided [Canvas] with the commands specified in the lambda with this
* [DrawScope] as a receiver
*
* @param canvas target canvas to render into
* @param size bounds relative to the current canvas translation in which the [DrawScope]
* should draw within
* @param block lambda that is called to issue drawing commands on this [DrawScope]
*/
inline fun draw(
density: Density,
layoutDirection: LayoutDirection,
canvas: Canvas,
size: Size,
block: DrawScope.() -> Unit
) {
// Remember the previous drawing parameters in case we are temporarily re-directing our
// drawing to a separate Layer/RenderNode only to draw that content back into the original
// Canvas. If there is no previous canvas that was being drawing into, this ends up
// resetting these parameters back to defaults defensively
val (prevDensity, prevLayoutDirection, prevCanvas, prevSize) = drawParams
drawParams.apply {
this.density = density
this.layoutDirection = layoutDirection
this.canvas = canvas
this.size = size
}
canvas.save()
this.block()//main
canvas.restore()
drawParams.apply {
this.density = prevDensity
this.layoutDirection = prevLayoutDirection
this.canvas = prevCanvas
this.size = prevSize
}
}
with(drawScope) {
with(modifier) {
draw()
}
}
private class Background constructor(
private val color: Color? = null,
private val brush: Brush? = null,
private val alpha: Float = 1.0f,
private val shape: Shape,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
override fun ContentDrawScope.draw() {
if (shape === RectangleShape) {
// shortcut to avoid Outline calculation and allocation
drawRect()
} else {
drawOutline()
}
drawContent()
}
override fun drawRect(
color: Color,
topLeft: Offset,
size: Size,
/*FloatRange(from = 0.0, to = 1.0)*/
alpha: Float,
style: DrawStyle,
colorFilter: ColorFilter?,
blendMode: BlendMode
) = drawParams.canvas.drawRect(
left = topLeft.x,
top = topLeft.y,
right = topLeft.x + size.width,
bottom = topLeft.y + size.height,
paint = configurePaint(color, style, alpha, colorFilter, blendMode)
)
override fun drawContent() {
drawIntoCanvas { canvas -> wrapped?.draw(canvas) }
}
inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)
override fun ContentDrawScope.draw() {
translate(dx, dy) {
with(painter) {
draw(size = scaledSize, alpha = alpha, colorFilter = colorFilter)
}
}
}
}
fun DrawScope.draw(
size: Size,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null
) {
inset(
left = 0.0f,
top = 0.0f,
right = this.size.width - size.width,
bottom = this.size.height - size.height
) {
if (alpha > 0.0f && size.width > 0 && size.height > 0) {
if (useLayer) {
val layerRect = Rect(Offset.Zero, Size(size.width, size.height))
// TODO (b/154550724) njawad replace with RenderNode/Layer API usage
drawIntoCanvas { canvas ->
canvas.withSaveLayer(layerRect, obtainPaint()) {
onDraw()
}
}
} else {
onDraw()
}
}
}
}
override fun DrawScope.onDraw() {
with(vector) {
draw(currentAlpha, currentColorFilter ?: intrinsicColorFilter)
}
// This conditional is necessary to obtain invalidation callbacks as the state is
// being read here which adds this callback to the snapshot observation
if (isDirty) {
isDirty = false
}
}
fun DrawScope.draw(alpha: Float, colorFilter: ColorFilter?) {
val targetColorFilter = if (colorFilter != null) {
colorFilter
} else {
intrinsicColorFilter
}
// If the content of the vector has changed, or we are drawing a different size
// update the cached image to ensure we are scaling the vector appropriately
if (isDirty || previousDrawSize != size) {
root.scaleX = size.width / viewportWidth
root.scaleY = size.height / viewportHeight
cacheDrawScope.drawCachedImage(
IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()),
this@draw,
layoutDirection,
drawVectorBlock
)
isDirty = false
previousDrawSize = size
}
cacheDrawScope.drawInto(this, alpha, targetColorFilter)
}
override fun drawPath(
path: Path,
brush: Brush,
/*FloatRange(from = 0.0, to = 1.0)*/
alpha: Float,
style: DrawStyle,
colorFilter: ColorFilter?,
blendMode: BlendMode
) = drawParams.canvas.drawPath(
path,
configurePaint(brush, style, alpha, colorFilter, blendMode)
)
override fun drawPath(path: Path, paint: Paint) {
internalCanvas.drawPath(path.asAndroidPath(), paint.asFrameworkPaint())
}
/**
* Describes the current state the [LayoutNode] is in.
*/
internal enum class LayoutState {
/**
* Request remeasure was called on the node.
*/
NeedsRemeasure,
/**
* Node is currently being measured.
*/
Measuring,
/**
* Request relayout was called on the node or the node was just measured and is going to
* layout soon (measure stage is always being followed by the layout stage).
*/
NeedsRelayout,
/**
* Node is currently being laid out.
*/
LayingOut,
/**
* Node is measured and laid out or not yet attached to the [Owner] (see [LayoutNode.owner]).
*/
Ready
}
/**
* A part of the composition that can be measured. This represents a layout.
* The instance should never be stored.
*/
interface Measurable : IntrinsicMeasurable {
/**
* Measures the layout with [constraints], returning a [Placeable] layout that has its new
* size. A [Measurable] can only be measured once inside a layout pass.
*/
fun measure(constraints: Constraints): Placeable
}
/**
* A part of the composition that can be measured. This represents a layout.
* The instance should never be stored.
*/
interface IntrinsicMeasurable {
/**
* Data provided by the `ParentData`
*/
val parentData: Any?
/**
* Calculates the minimum width that the layout can be such that
* the content of the layout will be painted correctly.
*/
fun minIntrinsicWidth(height: Int): Int
/**
* Calculates the smallest width beyond which increasing the width never
* decreases the height.
*/
fun maxIntrinsicWidth(height: Int): Int
/**
* Calculates the minimum height that the layout can be such that
* the content of the layout will be painted correctly.
*/
fun minIntrinsicHeight(width: Int): Int
/**
* Calculates the smallest height beyond which increasing the height never
* decreases the width.
*/
fun maxIntrinsicHeight(width: Int): Int
}
/**
* A [Placeable] corresponds to a child layout that can be positioned by its
* parent layout. Most [Placeable]s are the result of a [Measurable.measure] call.
*
* A `Placeable` should never be stored between measure calls.
*/
abstract class Placeable : Measured {
/**
* A [Measured] corresponds to a layout that has been measured by its parent layout.
*/
interface Measured {
/**
* The measured width of the layout. This might not respect the measurement constraints.
*/
val measuredWidth: Int
/**
* The measured height of the layout. This might not respect the measurement constraints.
*/
val measuredHeight: Int
/**
* Measurable and Placeable type that has a position.
*/
internal abstract class LayoutNodeWrapper(
internal val layoutNode: LayoutNode
) : Placeable(), Measurable, LayoutCoordinates, OwnerScope, (Canvas) -> Unit {
/**
* Measures the modified child.
*/
abstract fun performMeasure(constraints: Constraints): Placeable
https://developer.android.google.cn/jetpack/compose/layout#custom-layouts
https://developer.android.google.cn/jetpack/compose/graphics#drawscope