LayoutNode

Measure原理图

graph TB
XxModifier-->MeasureScope.measure("MeasureScope.measure")-->|1|measurable("measurable.measure(constraints)")
measurable-->|1|performMeasure
performMeasure-->|1|MeasureScope.measure
MeasureScope.measure-->|2|MeasureScope.layout

Draw原理图

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

LayoutNode类设计图

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

Modifier类设计图

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

Layout_Usage

Using the layout modifier

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)
    }
}

Creating custom layouts

@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
            }
        }
    }
}

Measure_Layout

AndroidComposeView.onMeasure

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
    }
}

convertMeasureSpec

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)
}

measureAndLayout

/**
 * 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.requestRemeasure

/** 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
    }
}

MeasureAndLayoutDelegate.doRemeasure

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
}

LayoutNode.remeasure

/**
 * 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.measure

/** LayoutNodeWrapper.kt
 * Measures the modified child.
 */
final override fun measure(constraints: Constraints): Placeable {
    measurementConstraints = constraints
    val result = performMeasure(constraints)
    layer?.resize(measuredSize)
    return result
}

ModifiedLayoutNode.performMeasure

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
    }

Modifier.paddin

//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
            }
        )
    )

PaddingModifier.MeasureScope.measure

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) {}
        }
    }
}

LayerNodeWrapper.placeAt

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

AndroidComposeView.createLayer

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)
    }
    
}

Draw

AndroidComposeView.dispatchDraw

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()
    }
}

LayoutNode.draw

internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)

LayoutNodeWapper.draw

/**
 * 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)
    }
}

DelegateLayoutWrapper.performDraw

override fun performDraw(canvas: Canvas) {
    wrapped.draw(canvas)
}

ViewLayer.drawLayer

override fun drawLayer(canvas: Canvas) {
    drawnWithZ = elevation > 0f
    if (drawnWithZ) {
        canvas.enableZ()
    }
    container.drawChild(canvas, this, drawingTime)//main
    if (drawnWithZ) {
        canvas.disableZ()
    }
}
ViewLayerContainer.drawChild
// 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
}

ViewLayer.dispatchDraw

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
    }
}

CanvasHolder.drawInto

/**
 * 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
    }
}

drawBlock: LayerNodeWrapper.invoke

// 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
    }
}
InnerPlaceable.performDraw
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)
    }
}
LayoutNode.Draw
internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)

    internal val outerLayoutNodeWrapper: LayoutNodeWrapper
        get() = outerMeasurablePlaceable.outerWrapper
ModifiedDrawNode.performDraw
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()
            }
        }
    }
}
LayoutNodeDrawScope.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
}
CanvasDrawScope.draw
/**
 * 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
    }
}
block()
with(drawScope) {
    with(modifier) {
        draw()
    }
}
Background.ContentDrawScope.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()
    }
CanvasDrawScope.drawRect
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)
)
AndroidCanvas.drawRect
LayoutNodeDrawScope.drawContent
override fun drawContent() {
    drawIntoCanvas { canvas -> wrapped?.draw(canvas) }
}

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)
PainterModifier.ContentDrawScope.draw
override fun ContentDrawScope.draw() {
        translate(dx, dy) {
            with(painter) {
                draw(size = scaledSize, alpha = alpha, colorFilter = colorFilter)
            }
        }
    }
}
Painter.DrawScope.draw
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()
            }
        }
    }
}
VectorPainter. DrawScope.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
    }
}
VectorComponent.DrawScope.draw
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)
}
CanvasDrawScope.drawPath
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)
)
AndroidCanvas.drawPath
override fun drawPath(path: Path, paint: Paint) {
    internalCanvas.drawPath(path.asAndroidPath(), paint.asFrameworkPaint())
}

其他

LayoutState

/**
 * 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
}

Measurable

/**
 * 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
}

IntrinsicMeasurable

/**
 * 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
}

Placeable

/**
 * 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 {

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

LayoutNodeWrapper

/**
 * 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