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,
)
}
}
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:
Related
I'm trying to set it so that a TextField refuses to shrink past its intrinsic height (defined by minLines), but also grow to fill max height:
// context: this is inside of a Column that has fillMaxHeight on it
OutlinedTextField(
// unrelated fields
minLines = 26,
modifier = Modifier
//.height(IntrinsicSize.Min)
.fillMaxWidth()
.fillMaxHeight()
.weight(1f)
.height(IntrinsicSize.Max)
)
However, everything I've tried (setting fillMaxHeight, weight(1f), using requiredHeight instead of height, etc) has issues. It seems like it's mutually exclusive: I can either set a minimum height based on intrinsic height or I can grow to fill the height (using weight seems to work for that, but not fillMaxHeight for some reason.
What is the proper way to have IntrinsicSize be the minimum, but grow to fill remaining space?
If i understand this question correctly you want your TextField height to be one line tall then increases as user enters text. You can selectively change modifier using Modifier.then()
Edit
As of jetpack compose 1.4.0-alpha02 minLines param is added to OutlineTextField and other text composables
#Composable
private fun Sample() {
var text by remember {
mutableStateOf("Hello World")
}
var focused by remember {
mutableStateOf(false)
}
Column(modifier = Modifier.fillMaxHeight()) {
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
minLines = 26,
// unrelated fields
modifier = Modifier
.fillMaxWidth()
.then(
if (focused) {
Modifier.wrapContentHeight()
} else Modifier.fillMaxHeight()
)
.onFocusChanged {
focused = it.isFocused
}
)
}
}
My code:
#ExperimentalMaterial3Api
#Composable
fun Settings() {
Card(modifier = Modifier
.fillMaxWidth()
) {
SettingItem(itemTitle = "Test Title", itemDescription = "Description")
}
}
#ExperimentalMaterial3Api
#Composable
fun SettingItem(itemTitle: String, itemDescription: String) {
Row(modifier = Modifier.padding(12.dp).fillMaxWidth()) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(text = itemTitle, color = MaterialTheme.colorScheme.primary)
Text(text = itemDescription, color = MaterialTheme.colorScheme.secondary)
}
Spacer(modifier = Modifier.size(width = 12.dp, height = 4.dp))
Switch(checked = false, onCheckedChange = {})
}
}
I want to put a Switch at the end of a SettingItem, so I set fillMaxWidth(). But the Switch shows out of the layout of Card.
Image
Change the Column modifier to weight.
From,
modifier = Modifier.fillMaxWidth()
To
modifier = Modifier.weight(1F)
The fillMaxWidth modifier is used to make the Column as wide as possible. Whereas here the requirement is to fill as wide as possible leaving space for the Spacer and Switch. That is achieved by using the weight modifier.
Weight
The parent will divide the horizontal space remaining after measuring unweighted child elements and distribute it according to this weight.
FillMaxWidth
Have the content fill (possibly only partially) the Constraints.maxWidth of the incoming measurement constraints, by setting the minimum width and the maximum width to be equal to the maximum width multiplied by fraction.
Note that, by default, the fraction is 1, so the modifier will make the content fill the whole available width.
Reference
weight
fillMaxWidth
How can I fit a text, inside a specific Box / Container (i.e of Size 100.dp)
Is there a way to Fit a string inside a Box, in Compose?
Here is what I tried:
#Composable
fun playText() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF242E38)),
contentAlignment = Alignment.Center
) {
Row(modifier = Modifier
.background(Color(0xFFEEEEEE))
.width(100.dp)
.height(100.dp)
) {
Text(
text = "FIT ME",
modifier = Modifier
.fillMaxSize()
.wrapContentSize(
Alignment.Center,
unbounded = true
)
.requiredSize(100.dp),
color = Color(0xFF242E38),
fontSize = 50.sp,
style = TextStyle.Default.copy(
background = Color(0xFFEAC43D)
)
)
}
}
}
#Preview(showBackground = true)
#Composable
fun show() {
playText()
}
Here is the current result:
Here is the expected result (which I achieved by decreasing the font size, just to demonstrate what is expected):
In jetpack Compose, you can Justify a Text like this:
Text(
text = text,
textAlign = TextAlign.Justify
)
If you want support RTL, you achive this by:
Text(
text = text,
textAlign = TextAlign.Right
)
How can a Text() support RTL text and justify it same time in Jetpack Compose?
After several hour testing, I reach this:
#Composable
fun JustifiedRTLText(
text: String,
modifier: Modifier = Modifier
) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Text(
text = text,
textAlign = TextAlign.Justify,
modifier = modifier,
)
}
}
In Jetpack Compose RTL or LTR will automatically set by according to the text content. But we can force it by changing the textDirection of style to TextDirection.Content or TextDirection.RTL
Text(
text = text,
textAlign = TextAlign.Justify,
style = TextStyle(textDirection = TextDirection.Content)
)
I have to create a reusable component that tries to achieve this goal: I have a column that can have content that's larger than the screen height. On the bottom of the screen we have panel with gradient background that can contain button or something else (it's basically a slot in the component). This bottom panel have to be always visible on the screen, and in case of the column being bigger than screen - bottom panel have to be on the top of this column. Gradient background does a nice UX effect so user knows what is going on. It looks like that:
I have that solved, but here's the challenge. The column content have to be scrollable to be on top of the bottom panel when scrolled to the end. Current solution I have is to add a spacer on the bottom of this column. This spacer have the calculated height of the bottom parent. And here's the issue - right now we have calculation done in onSizeChanged which basically results in additional frame needed for the spacer to have correct size.
We did not observe any negative impact of that performance or UX wise. The spacer height calculation never does anything that user can see, but I still want to solve that properly.
AFAIK this can be done using custom Layout, but that seems a little bit excessive for what I want to achieve. Is there another way to do this properly?
Current solution:
#Composable
fun FloatingPanelColumn(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
contentHorizontalAlignment: Alignment.Horizontal = Alignment.Start,
bottomPanelContent: #Composable ColumnScope.() -> Unit,
content: #Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
var contentSize by remember {
mutableStateOf(1)
}
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.then(contentModifier),
horizontalAlignment = contentHorizontalAlignment,
) {
content()
val contentSizeInDp = with(LocalDensity.current) { contentSize.toDp() }
Spacer(modifier = Modifier.height(contentSizeInDp))
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.onSizeChanged {
contentSize = it.height
}
.wrapContentHeight()
.align(Alignment.BottomStart)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0x00FAFCFF),
Color(0xFFF6F9FB),
)
)
),
content = bottomPanelContent
)
}
}
The best way to depend on an other view size during layout is using SubcomposeLayout:
SubcomposeLayout { constraints ->
// subcompose the view you need to measure first
val bottomPanel = subcompose("bottomPanel") {
Column(
// ...
)
}[0].measure(constraints)
// use calculated value in next views layout, like bottomPanel.height
val mainList = subcompose("mainList") {
LazyColumn(
contentPadding = PaddingValues(bottom = bottomPanel.height.toDp())
) {
// ...
}
}[0].measure(constraints)
layout(mainList.width, mainList.height) {
mainList.place(0, 0)
bottomPanel.place(
(mainList.width - bottomPanel.width) / 2,
mainList.height - bottomPanel.height
)
}
}