scrollable Text crashes

1.1k Views Asked by At

I want Text to be of max 4 lines but scrollable, as the following. Is that the way to do it?

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(vertical = 6.dp, horizontal = 8.dp)
        .verticalScroll(rememberScrollState()),
    horizontalAlignment = Alignment.Start,
    verticalArrangement = Arrangement.spacedBy(5.dp)
    ) {
        Row {
            Text(
                text = longText,
                maxLines = 4,
                modifier = Modifier
                    .padding(end = 5.dp)
                    .verticalScroll(rememberScrollState(0)),
                textAlign = TextAlign.Start,
                fontSize = dimensionResource(dimen.normal_text).value.sp,
            )
       }
}

But I got crash messages. I don't have a lazycolumn. I would like Column to be scrollable as well. That can not be done at the same time?

    E/AndroidRuntime: FATAL EXCEPTION: main
    java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
        at androidx.compose.foundation.ScrollKt.assertNotNestingScrollableContainers-K40F9xA(Scroll.kt:383)
        at androidx.compose.foundation.ScrollingLayoutModifier.measure-3p2s80s(Scroll.kt:337)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:40)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
        at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:405)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:40)
        at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(Padding.kt:364)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:40)
        at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1342)
        at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1341)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:88)

  .......

        at android.os.Looper.loop(Looper.java:216)
        at android.app.ActivityThread.main(ActivityThread.java:7625)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)

Update: I found if I don't limit the number of lines but the height of the composable, like the following, then scrolling works, without crash.

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(vertical = 6.dp, horizontal = 8.dp)
        .verticalScroll(rememberScrollState()),
    horizontalAlignment = Alignment.Start,
    verticalArrangement = Arrangement.spacedBy(5.dp)
    ) {
        Row {
            Text(
                text = longText,
                modifier = Modifier
                    .padding(end = 5.dp)
                    .height(100.dp)
                    .verticalScroll(rememberScrollState(0)),
                textAlign = TextAlign.Start,
                fontSize = dimensionResource(dimen.normal_text).value.sp,
            )
       }
}
1

There are 1 best solutions below

3
On

I think you can use SubcomposeLayout for this.

val longText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
Column(
    /* I checked with your Column params and it worked */
) {
    Row {
        val localDensity = LocalDensity.current
        SubcomposeLayout { constraints ->
            // This modifier must be used in the fake text and real content
            // if you need to set text size, you should use it on both contents
            val textModifier = Modifier.padding(6.dp)
            // Here it's calculated the height of a Text with 4 lines
            val fourLinesHeight = subcompose("SomeRandomId") {
                Text(text = "A\nB\nC\nD", modifier = textModifier)
            }.first().measure(constraints).height
            // The height is in pixels. Converting to Dp
            val heightInDp = with(localDensity) { (fourLinesHeight / density).dp }
            // Now the real content is measured. 
            val placeables = subcompose("RealContentSlotId") {
                Text(
                    text = longText,
                    modifier = Modifier
                        .height(heightInDp) // here is the secret
                        .then(textModifier)
                        .verticalScroll(rememberScrollState(0)),
                    textAlign = TextAlign.Start,
                )
            }.first().measure(constraints)
            layout(placeables.width, placeables.height) {
                placeables.placeRelative(0, 0)
            }
        }
    }
}

Here is the result:

enter image description here