<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nam’s node]]></title><description><![CDATA[the simple, the best]]></description><link>https://wip.ene.im</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 18:56:18 GMT</lastBuildDate><atom:link href="https://wip.ene.im/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Jetpack Compose - Modifier.layout (Part 2)]]></title><description><![CDATA[In part 1, we learn the basic of Modifier.layout using a sample from the official document. In part 2, we explore the AlignmentLine concept to answer the following questions: what is it and how to use it.
The AlignmentLine
In the previous part, we ha...]]></description><link>https://wip.ene.im/jetpack-compose-modifierlayout-part-2</link><guid isPermaLink="true">https://wip.ene.im/jetpack-compose-modifierlayout-part-2</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Nam Nguyen]]></dc:creator><pubDate>Sun, 08 Aug 2021 14:25:07 GMT</pubDate><content:encoded><![CDATA[<p>In <a target="_blank" href="/jetpack-compose-modifierlayout-part-1">part 1</a>, we learn the basic of <code>Modifier.layout</code> using a sample from the official document. In part 2, we explore the <code>AlignmentLine</code> concept to answer the following questions: what is it and how to use it.</p>
<h1 id="the-alignmentline">The AlignmentLine</h1>
<p>In the previous part, we had the following custom layout:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { measurable, constraints -&gt;
    <span class="hljs-comment">// Measure the composable</span>
    <span class="hljs-keyword">val</span> placeable = measurable.measure(constraints)

    <span class="hljs-comment">// Check the composable has a first baseline and extract it</span>
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    <span class="hljs-keyword">val</span> firstBaseline = placeable[FirstBaseline]

    <span class="hljs-comment">// Calculate: the total height of the composable after applying the firstBaselineToTop value is equal to its original height plus the diff between the firstBaselineToTop value and the first baseline, which is:</span>
    <span class="hljs-keyword">val</span> totalHeight = placeable.height + 
        (firstBaselineToTop.roundToPx() - firstBaseline)
    <span class="hljs-comment">// Therefore, the starting point from top by which we place the composable is:</span>
    <span class="hljs-keyword">val</span> placeableY = totalHeight - placeable.height
    layout(placeable.width, totalHeight) {
        <span class="hljs-comment">// Now place it</span>
        placeable.placeRelative(<span class="hljs-number">0</span>, placeableY)
    }
}
</code></pre>
<p>Let's focus on this part:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Check the composable has a first baseline and extract it</span>
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
<span class="hljs-keyword">val</span> firstBaseline = placeable[FirstBaseline]
</code></pre>
<p>There is barely an explanation for it. It turns out that, here, we extract the value exported by the <em>placeable</em>, and use it to calculate and lay our composable out. This is mentioned in the <a target="_blank" href="https://developer.android.com/jetpack/compose/layouts/alignment-lines">document</a>:</p>
<blockquote>
<p>The Compose layout model lets you use AlignmentLine to create custom alignment lines that can be used by parent layouts to align and position their children. For example, Row can use its children's custom alignment lines to align them.</p>
<p>When a layout provides a value for a particular AlignmentLine, the layout's parents can read this value after measuring, using the Placeable.get operator on the corresponding Placeable instance. Based on the position of the AlignmentLine, the parents can then decide the positioning of the children.</p>
</blockquote>
<p>To better understand this, let consider this question: given a parent layout <strong>X</strong> with 3 children: <strong>A, B, C</strong>. How can we align <strong>B</strong> and <strong>C</strong> relatively to some value calculated by laying out <strong>A</strong>?</p>
<p>Based on the document, we can do this using <code>AlignmentLine</code>. But how?</p>
<p>To demonstrate the solution, consider this example: </p>
<ul>
<li><strong>A</strong> is a block of text, which has the first baseline value (distance from the top of the text to the first baseline in pixel) and the last baseline value (distance from the top of the text block to the baseline of that last line in pixel).</li>
<li><strong>B</strong> is a text block whose first baseline is aligned horizontally with the first baseline of <strong>A</strong>,</li>
<li>and <strong>C</strong> is another text block whose first baseline is aligned horizontally with the last baseline of <strong>A</strong>.</li>
<li><strong>X</strong> is the custom layout we build to fulfill our requirement:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628429812035/uVjuIakPB.png" alt="Screen Shot 2021-08-08 at 22.36.20.png" /></p>
<p>The full code of this example is as below (with some adjustment to draw lines for our verification):</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TextWithBaselineIndicator</span><span class="hljs-params">(
  modifier: <span class="hljs-type">Modifier</span> = Modifier,
  topLine: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
  bottomLine: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
  content: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
)</span></span> {
  Layout(
    content = {
      content()
      Text(text = <span class="hljs-string">"first baseline"</span>)
      Text(text = <span class="hljs-string">"last baseline"</span>)
      topLine()
      bottomLine()
    },
    modifier = Modifier.then(modifier)
  ) { measurables: List&lt;Measurable&gt;, constraints: Constraints -&gt;
    check(measurables.size == <span class="hljs-number">5</span>)
    <span class="hljs-keyword">val</span> placeables = measurables.map {
      it.measure(constraints = constraints)
    }

    <span class="hljs-keyword">val</span> mainBlock = placeables[<span class="hljs-number">0</span>]

    <span class="hljs-keyword">val</span> firstBaseline = mainBlock[FirstBaseline]
    <span class="hljs-keyword">val</span> firstBaselineText = placeables[<span class="hljs-number">1</span>]
    <span class="hljs-keyword">val</span> firstBaselineTextY =
      firstBaseline - firstBaselineText[FirstBaseline]

    <span class="hljs-keyword">val</span> lastBaseline = mainBlock[LastBaseline]
    <span class="hljs-keyword">val</span> lastBaselineText = placeables[<span class="hljs-number">2</span>]
    <span class="hljs-keyword">val</span> lastBaselineTextY =
      lastBaseline - lastBaselineText[FirstBaseline]

    layout(
      width = constraints.maxWidth,
      height = constraints.maxHeight
    ) {
      <span class="hljs-comment">// The texts that are aligned with the baselines</span>
      firstBaselineText.placeRelative(<span class="hljs-number">0</span>, firstBaselineTextY)
      lastBaselineText.placeRelative(<span class="hljs-number">0</span>, lastBaselineTextY)

      <span class="hljs-comment">// Red lines</span>
      placeables[<span class="hljs-number">3</span>].placeRelative(<span class="hljs-number">0</span>, firstBaseline)
      placeables[<span class="hljs-number">4</span>].placeRelative(<span class="hljs-number">0</span>, lastBaseline)

      <span class="hljs-comment">// The main text block</span>
      mainBlock.placeRelative(
        max(firstBaselineText.width, lastBaselineText.width) + <span class="hljs-number">16</span>,
        <span class="hljs-number">0</span>
      )
    }
  }
}

<span class="hljs-comment">// Usage</span>
TextWithBaselineIndicator(
  modifier = Modifier,
  topLine = {
    Canvas(modifier = Modifier.fillMaxWidth()) {
      <span class="hljs-keyword">val</span> canvasWidth = size.width
      <span class="hljs-keyword">val</span> canvasHeight = size.height
      drawLine(
        start = Offset(x = canvasWidth, y = <span class="hljs-number">0f</span>),
        end = Offset(x = <span class="hljs-number">0f</span>, y = canvasHeight),
        color = Color.Red
      )
    }
  },
  bottomLine = {
    Canvas(modifier = Modifier.fillMaxWidth()) {
      <span class="hljs-keyword">val</span> canvasWidth = size.width
      <span class="hljs-keyword">val</span> canvasHeight = size.height
      drawLine(
        start = Offset(x = canvasWidth, y = <span class="hljs-number">0f</span>),
        end = Offset(x = <span class="hljs-number">0f</span>, y = canvasHeight),
        color = Color.Red
      )
    }
  }
) {
  Text(
    text = <span class="hljs-string">"This is a really long text.\nThis is a really long text.\nThis is a really long text.\nThis is a really long text."</span>,
    fontSize = <span class="hljs-number">24</span>.sp,
  )
}
</code></pre>
<p>As you can see, we extract the information about the first baseline and last baseline of the main text by calling:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> firstBaseline = mainBlock[FirstBaseline]
<span class="hljs-keyword">val</span> lastBaseline = mainBlock[LastBaseline]
</code></pre>
<p>So what does that code do?</p>
<p>We can see that, a <code>Placeable</code> is also a <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Measured.kt"><code>Measured</code></a>, so <code>placeable[FirstBaseline]</code> is equal to <code>placeable.get(FirstBaseline)</code>, which "returns the position of an alignment line, or <code>AlignmentLine.Unspecified</code> if the line is not provided." (<a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Measured.kt;l=34">operator fun get(alignmentLine: AlignmentLine): Int</a>). But where does the <code>FirstBaseline</code> come from and why <code>placeable[FirstBaseline]</code> returns the value we need? Reading the source code, we learn that:</p>
<ul>
<li><p><a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/AlignmentLine.kt;l=102?q=FirstBaseline&amp;ss=androidx%2Fplatform%2Fframeworks%2Fsupport"><code>FirstBaseline</code></a> is a built-in <code>AlignmentLine</code>.</p>
</li>
<li><p>And a Text composable exposes that value via <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt;l=303?q=CoreText&amp;ss=androidx%2Fplatform%2Fframeworks%2Fsupport">the layout call</a>.</p>
</li>
</ul>
<p>Now, go back to our original question: what is <code>AlignmentLine</code> and how we can use it. From the example above, we can <em>guess</em> that: an <code>AlignmentLine</code> is a value exposed by a composable so that the parent composable can use it to align the children composable accordingly. A <code>Text</code> composable exposes 2 alignment lines: <code>FirstBaseline</code> and <code>LastBaseline</code> by which we can build custom layouts like above. A custom <code>AlignmentLine</code> can be exposed by passing them to the <code>layout(width, height, alignmentLines)</code> callback in the custom layout implementation. </p>
<p>In the next part, we will learn about the <code>AlignmentLine</code> further, by going through the official document. </p>
]]></content:encoded></item><item><title><![CDATA[Jetpack Compose - Modifier.layout (Part 1)]]></title><description><![CDATA[Reference: https://developer.android.com/jetpack/compose/layouts/custom#layout-modifier
The simple Modifier.layout from sample code
Let's replicate the Modifier.firstBaselineToTop in the official document, step by step to understand how it works.
The...]]></description><link>https://wip.ene.im/jetpack-compose-modifierlayout-part-1</link><guid isPermaLink="true">https://wip.ene.im/jetpack-compose-modifierlayout-part-1</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Nam Nguyen]]></dc:creator><pubDate>Sat, 07 Aug 2021 13:34:39 GMT</pubDate><content:encoded><![CDATA[<p>Reference: <a target="_blank" href="Using the layout modifier">https://developer.android.com/jetpack/compose/layouts/custom#layout-modifier</a></p>
<h1 id="the-simple-modifierlayout-from-sample-code">The simple <code>Modifier.layout</code> from sample code</h1>
<p>Let's replicate the <code>Modifier.firstBaselineToTop</code> in the official document, step by step to understand how it works.</p>
<p>The final form:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { measurable, constraints -&gt;
    <span class="hljs-comment">// Measure the composable</span>
    <span class="hljs-keyword">val</span> placeable = measurable.measure(constraints)

    <span class="hljs-comment">// Check the composable has a first baseline</span>
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    <span class="hljs-keyword">val</span> firstBaseline = placeable[FirstBaseline]

    <span class="hljs-comment">// Height of the composable with padding - first baseline</span>
    <span class="hljs-keyword">val</span> placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    <span class="hljs-keyword">val</span> height = placeable.height + placeableY
    layout(placeable.width, height) {
        <span class="hljs-comment">// Where the composable gets placed</span>
        placeable.placeRelative(<span class="hljs-number">0</span>, placeableY)
    }
}
</code></pre>
<p>And its result:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://developer.android.com/images/jetpack/compose/layout-padding-baseline.png" alt="firstBaselineToTop example (from official document)" /></td></tr>
</thead>
<tbody>
<tr>
<td>Source: <a target="_blank" href="https://developer.android.com/jetpack/compose/layouts/custom#layout-modifier">official document</a></td></tr>
</tbody>
</table>
</div><h3 id="step-1-the-do-nothing-attempt">Step 1: the do-nothing attempt</h3>
<p>In the first step, let's just add the bare minimum code that does nothing:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { _: Measurable, _: Constraints -&gt;
    <span class="hljs-comment">// Usually, we need to measure the composable and get the `placeable`, but in this step, we will ignore it, and pass 0 as width and height to the layout callback.</span>
    layout(width = <span class="hljs-number">0</span>, height = <span class="hljs-number">0</span>) { <span class="hljs-comment">/* Do nothing */</span> }
}
</code></pre>
<p>And verify the UI by using the Android Studio layout inspector. In this step, it is easy to imagine that there is nothing shown in the UI. Let's skip this step.</p>
<h3 id="step-2-measure-it-lay-it-out-but-dont-place-it-there">Step 2: measure it, lay it out, but don't place it there.</h3>
<p>What we do is to measure the composable, pass the measured width and height to the <code>layout</code> callback in the lambda.:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { measurable: Measurable, constraints: Constraints -&gt;
    <span class="hljs-comment">// Measure the composable and get the placeable, using the provided measurable and constraints</span>
    <span class="hljs-keyword">val</span> placeable = measurable.measure(constraints)
    layout(width = placeable.width, height = placeable.height) { <span class="hljs-comment">/* Do nothing */</span> }
}
</code></pre>
<p>Now let's check the UI. It is still a blank space in the screen, but if we inspect it, there is <em>something</em> populates the space:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628342165911/mDTMWeddH.png" alt="Screen Shot 2021-08-07 at 22.15.56.png" /></p>
<p>Adjusting the width or height above, we can conclude that: the <code>layout</code> callback will tell the parent to reserve a rectangle whose width and height are provided value, and its coordinate starts from (0, 0). This rectangle will be the boundary of our composable after laying out. Indeed, if we update out code to the following and check the result:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { measurable: Measurable, constraints: Constraints -&gt;
    <span class="hljs-comment">// Measure the composable and get the placeable, using the provided measurable and constraints</span>
    <span class="hljs-keyword">val</span> placeable = measurable.measure(constraints)
    layout(width = placeable.width, height = placeable.height) {
        placeable.placeRelative(x = <span class="hljs-number">20</span>, y = <span class="hljs-number">20</span>)
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628342421106/-2vpfufJJ.png" alt="Screen Shot 2021-08-07 at 22.20.12.png" /></p>
<p>The layout inspector shows that: our text is placed 20 pixel from the left, and 20 pixel from the top, and it is cut at the edge of the dimension we provided above.</p>
<p>To be able to correctly place the composable, we need to adjust the space required by it. It is time to take the <code>firstBaselineToTop</code> into account.</p>
<h3 id="step-3-measure-it-reserve-space-for-it-and-place-it">Step 3: measure it, reserve space for it and place it</h3>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">firstBaselineToTop</span><span class="hljs-params">(
    firstBaselineToTop: <span class="hljs-type">Dp</span>
)</span></span> = layout { measurable, constraints -&gt;
    <span class="hljs-comment">// Measure the composable</span>
    <span class="hljs-keyword">val</span> placeable = measurable.measure(constraints)

    <span class="hljs-comment">// Check the composable has a first baseline and extract it</span>
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    <span class="hljs-keyword">val</span> firstBaseline = placeable[FirstBaseline]

    <span class="hljs-comment">// Calculate: the total height of the composable after applying the `firstBaselineToTop` value is equal to its original height plus the diff between the</span>
`firstBaselineToTop` value and the first baseline, which <span class="hljs-keyword">is</span>:
    <span class="hljs-keyword">val</span> totalHeight = placeable.height + (firstBaselineToTop.roundToPx() - firstBaseline)
    <span class="hljs-comment">// Therefore, the starting point from top by which we place the composable is:</span>
    <span class="hljs-keyword">val</span> placeableY = totalHeight - placeable.height
    layout(placeable.width, totalHeight) {
        <span class="hljs-comment">// Now place it</span>
        placeable.placeRelative(<span class="hljs-number">0</span>, placeableY)
    }
}
</code></pre>
<p>And tada:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628342827388/g31E34O3E.png" alt="Screen Shot 2021-08-07 at 22.26.55.png" /></p>
<h1 id="exploring-the-layout-callback">Exploring the <code>layout</code> callback</h1>
<p>It seems that the <code>layout</code> callback is one of the core actor here. Let's learn more about it:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">MeasureScope</span> : <span class="hljs-type">IntrinsicMeasureScope {</span></span>
    <span class="hljs-comment">/**
     * <span class="hljs-doctag">@param</span> alignmentLines the alignment lines defined by the layout
     * <span class="hljs-doctag">@param</span> placementBlock block defining the children positioning of the current layout
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">layout</span><span class="hljs-params">(
        width: <span class="hljs-type">Int</span>,
        height: <span class="hljs-type">Int</span>,
        alignmentLines: <span class="hljs-type">Map</span>&lt;<span class="hljs-type">AlignmentLine</span>, <span class="hljs-built_in">Int</span>&gt; = emptyMap()</span></span>,
        placementBlock: Placeable.PlacementScope.() -&gt; <span class="hljs-built_in">Unit</span>
    ) = <span class="hljs-keyword">object</span> : MeasureResult {
        <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> width = width
        <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> height = height
        <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> alignmentLines = alignmentLines
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">placeChildren</span><span class="hljs-params">()</span></span> {
            Placeable.PlacementScope.executeWithRtlMirroringValues(
                width,
                layoutDirection,
                placementBlock
            )
        }
    }
}
</code></pre>
<p>So beside the width, the height and the <code>placementBlock</code> lambda we used to place the placeable, there is <code>alignmentLines</code> values which is default to an empty map. Let's learn about this value in part 2.</p>
]]></content:encoded></item></channel></rss>