0

I want to use a Composable as InlineTextContent in an AnnotatedString. This requires supplying a width and height to the Placeholder. Is there a way to pre-measure my Composable to know the width and height before it is swapped in and displayed?

1 Answers1

1

I guess that the reason Placeholder asks for size that you can pass something like 1.ep value to height - in this case it's gonna be equal to the font size height, so you can scale your content accordingly.

But generally for such purpose I'm using SubcomposeLayout - you can measure any view size and subcompose actual view depending on the result:

@Composable
fun MeasureViewSize(
    viewToMeasure: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable (DpSize) -> Unit,
) {
    SubcomposeLayout(modifier = modifier) { constraints ->
        val measuredSize = subcompose("viewToMeasure") {
            viewToMeasure()
        }[0].measure(constraints)
            .let {
                DpSize(
                    width = it.width.toDp(),
                    height = it.height.toDp()
                )
            }

        val contentPlaceable = subcompose("content") {
            content(measuredSize)
        }.firstOrNull()?.measure(constraints)

        layout(contentPlaceable?.width ?: 0, contentPlaceable?.height ?: 0) {
            contentPlaceable?.place(0, 0)
        }
    }
}

To use it with InlineTextContent you also need to convert Dp to Sp:

@Composable
fun View() {
    val myId = "inlineContent"
    val text = buildAnnotatedString {
        append("Hello")
        // Append a placeholder string "[myBox]" and attach an annotation "inlineContent" on it.
        appendInlineContent(myId, "[myBox]")
    }
    val density = LocalDensity.current

    MeasureViewSize(
        viewToMeasure = {
            ViewToInline()
        },
    ) { measuredSize ->
        val inlineContent = mapOf(
            Pair(
                // This tells the [BasicText] to replace the placeholder string "[myBox]" by
                // the composable given in the [InlineTextContent] object.
                myId,
                InlineTextContent(
                    // Placeholder tells text layout the expected size and vertical alignment of
                    // children composable.
                    with(density) {
                        Placeholder(
                            width = measuredSize.width.toSp(),
                            height = measuredSize.height.toSp(),
                            placeholderVerticalAlign = PlaceholderVerticalAlign.Center
                        )
                    }
                ) {
                    ViewToInline()
                }
            )
        )

        BasicText(
            text = text, 
            inlineContent = inlineContent,
        )
    }
}

@Composable
private fun ViewToInline() {
    Box(
        modifier = Modifier
            .height(100.dp)
            .aspectRatio(0.5f)
            .background(color = Color.Red)
    )
}

Result:

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220