How to add compose modifiers based on other surrounding views? - android

I am unable to get the expected results as shown in the picture below. There are 2 rules to follow
The horizontal line should not continue till the bottom text. Instead, it should just be the height of the right text (multiline).
Bottom text should align with the Right Text from the left side.
Current Incorrect Snippet
#Composable
fun Sample() {
Row(
modifier = Modifier
.height(IntrinsicSize.Min)
.padding(10.dp)
) {
Text("Left Text")
Divider(
Modifier
.padding(horizontal = 10.dp)
.fillMaxHeight()
.width(4.dp),
color = Color.Black
)
Column {
Text("Right Looooong Text")
Text("Bottom Text")
}
}
}
Visual Representation

You can achieve this in various ways including
Option 1: You can either redesign your Composable
Option 2: Apply Modifier.layoutId() your Composables then set their position relative to each other using Layout and getting measurables via this ids then placing them based on one that they depend on.
I post only the option one which is the easiest one.
#Composable
fun Sample(horizontalPadding: Dp = 10.dp, dividerWidth: Dp = 4.dp) {
Row(
modifier = Modifier.padding(10.dp)
) {
Text("Left Text")
Column {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
Divider(
Modifier
.padding(horizontal = horizontalPadding)
.fillMaxHeight()
.width(dividerWidth),
color = Color.Black
)
Text("Right Loooooooooooooooooooong Text")
}
Text(
"Bottom Text",
modifier = Modifier.offset(x = horizontalPadding * 2 + dividerWidth)
)
}
}
}
Result

You can use the modifier extension .onSizeChanged{}, which returns its composable's size in Int, to set the vertical divider's height as follows:
var divHeight: Int = 0 // Must be initialized
Row() {
Text(text = "Left Text")
Divider(
modifier = Modifier
.padding(horizontal = 10.dp)
.width(4.dp)
.height(divHeight.dp) // .dp converts measurement from Int to Dp
)
Column {
Text(
modifier = Modifier
.onSizeChanged { divHeight = it.height },
text = "Right Looooong Text"
)
Text(text = "Bottom Text")
}
}
Hope you found this helpful!

Related

How to achieve multiple header and body elements list for a LazyColumn in compose?

I want to achieve the following layout
All the headers like Claim requested credentials, Claim received credentials, Pending requests etc are dynamic and will be coming from backend and the items for each header like Module 1:... etc are dynamic as well. Also I need the card to encapsulate both Header and the Body item.
Here is what I have tried
LazyColumn(
modifier = Modifier
.offset(x = 0.dp, y = 110.dp)
.fillMaxSize()
.background(
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
color = MaterialTheme.colors.primaryVariant
)
.padding(12.dp)
) {
itemsIndexed(
listOf(
"Claim requested credentials",
"Claim received credentials",
"Pending Requests"
)
) { index, item ->
Card(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
)
,elevation = 20.dp,
contentColor = MaterialTheme.colors.primaryVariant,
backgroundColor = MaterialTheme.colors.primaryVariant
) {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp)) {
ManageCredentialHeader(text = item, vcsNo = 2)
LazyColumn(){
itemsIndexed(listOf(1,2,3)){ index, item ->
ManageCredentialItem()
}
}
}
}
}
}
But I get an error saying 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.
How to achieve this kind of dynamic layout with LazyColumn in compose. In react-native this was easily achieved using SectionList.
In Jetpack Compose when you create layouts like LazyColumn or any other layout you measure your Composables as Measurables with the limits coming from Constraints as min/max width/height. Modifier.scroll or LazyList returns max as Constraints.Infinity which is not permitted for child LazyColumn. That's why you get that exception.
In this answer i explained which modifier returns which Constraints. You can check out Constraints section.
Since you don't have a height for the Column add a size modifier that changes maxHeight of inner LazyColumn from Constraints.Infinity to something finite such as
Column(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.heightIn(max = 500.dp) {
}
max height is not the fixed value set, it's the biggest height that child LazyColumns can get. You can change it as required. Your items can grow in height up to this.
Implementation
#Composable
private fun LazyListSample() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
color = Color(0xffE3F2FD)
)
.padding(12.dp),
contentPadding = PaddingValues(vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
itemsIndexed(
listOf(
"Claim requested credentials",
"Claim received credentials",
"Pending Requests"
)
) { index, item ->
Card(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding(),
elevation = 20.dp,
shape = RoundedCornerShape(10.dp),
) {
Column(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.heightIn(max = 500.dp)
) {
ManageCredentialHeader(text = item, vcsNo = 2)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(listOf(1, 2, 3)) { index, item ->
ManageCredentialItem()
}
}
}
}
}
}
}
#Composable
private fun ManageCredentialHeader(text: String, vcsNo: Int) {
Row(modifier = Modifier.fillMaxWidth()) {
Text(text)
Spacer(modifier = Modifier.width(4.dp))
Box( modifier = Modifier
.size(24.dp)
.background(Color.Blue,CircleShape)
.padding(2.dp),
contentAlignment = Alignment.Center
) {
Text(
vcsNo.toString(),
color = Color.White
)
}
Spacer(modifier = Modifier.weight(1f))
}
}
#Composable
private fun ManageCredentialItem() {
Row(modifier = Modifier.padding(top = 16.dp)) {
Column() {
Text("Module 1 some text")
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text("Subtitle")
}
}
Spacer(modifier = Modifier.weight(1f))
Text(
"VIEW",
modifier = Modifier
.background(Color(0xff283593), RoundedCornerShape(8.dp))
.padding(6.dp),
color = Color.White
)
}
}
Looks like you need a single dynamic LazyColumn here.
You don't have to only use items at the topmost level - if you check out its source code, you'll see that it's just adds single item in a loop.
You can build your lazy content by adding multiple items inside an other loop, something like this:
LazyColumn {
listOf(
"Claim requested credentials",
"Claim received credentials",
"Pending Requests"
).forEach { header ->
item(
contentType = "header"
) {
Text(header)
}
items(
count = 10,
contentType = "body",
) { bodyIndex ->
Text(bodyIndex.toString())
}
}
}
I think problem there
LazyColumn(){
itemsIndexed(listOf(1,2,3)){ index, item ->
ManageCredentialItem()
}
}
You cannot nest LasyColumn inside LazyColumn, but you can replace it like this:
Column {
for (index, item in listOf(1, 2, 3).withIndex()) {
ManageCredentialItem()
}
}

Kotlin Jetpack compose Center text in Column inside a LazyColum

I'am new to jetpack compose and i really liked it. But ran into a problem : I want to create a card and inside of it, i would like to display a list of item with divider between them. I was almost able to achieved it here is my code :
Box(modifier = Modifier.background(Color(0xFFf4f4f4))
.fillMaxSize()
.padding(top = 20.dp)) {
Card(
modifier = Modifier
.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
elevation = 5.dp
) {
val colorNamesList = listOf("Red", "Green", "Blue", "Indigo")
LazyColumn() {
itemsIndexed(
colorNamesList,
) { index, item ->
Column(
modifier = Modifier
.background(
color = Color(0xFFf2f2f2),
)
.clickable {
println(item)
},
horizontalAlignment = Alignment.CenterHorizontally,//those 2 does nothing
verticalArrangement = Arrangement.Center //when i change it nothing change
) {
println(item + index)
Text(
text = "Item at $item",
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
//textAlign = TextAlign.Center, without it image 1, with it image 2
color = Color.Black,
)
if (index < colorNamesList.lastIndex) {
Divider(
color = Color.Black.copy(alpha = 0.2f),
modifier = Modifier
.padding(horizontal = 80.dp),
)
}
}
}
}
}
}
It's almost good but i didn't managed to align the text inside it, i searched some answer but Alignment and Arrangement doesn't change anything in my situation.
I was able to align horizontally by adding textAlign = TextAlign.Center as i commented in my code but, i couldn't align vertically, the text stay on the top of his cell :
What i want to do is text center in every cell of the column. Thanks for the help.
You can achieve this by adding similar xml attribute like "wrap_content" as height in compose too.
Add "wrapContentHeight()" to modifier and you will get it to align to centre of the page.
Text(
text = "Item at $item",
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.wrapContentHeight(),
textAlign = TextAlign.Center
color = Color.Black,
)

How to align Text to Top, Bottom and Center Vertically in Jetpack Compose?

How can I align the text using Text composable function vertically. Is there a way to do it without having to add another extra view to contain the Text.
The textAlign parameter of Text only has the following options:
TextAlign.
Left
Right
Center
Justify
Start
End
I have tried using textAlign = TextAlign.Center but it only centers it horizontally. How can I center it vertically without wrapping it in another view?
Text(
text = "Text",
modifier = Modifier.size(100.dp),
textAlign = TextAlign.Center
)
Result:
What I am trying to achieve:
You have to use a parent container and align the composable inside it.
For example a Box:
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Center
) {
Text(
text = "Text",
)
}
or a Column:
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Text",
)
}
first use .wrapContentHeight() in modifier, then you can use align(CenterVertically) param to center the text.
It is difficult to give an exact answer without seeing your code, but I would say you need to use container's Alignment.CenterVertically instead of TextAlign
Something like:
Row {
Text(
text = "Text",
modifier = Modifier.align(alignment = Alignment.CenterVertically)
)
Image(
...
)
}
or:
Column(
modifier = Modifier
.align(Alignment.CenterVertically)
) {
Text(text = "Text")
Text(text = "Text 2")
}
I asked the same question here which is done with android:gravity param using views, as answered by PhilipDukhov it's not possible only with Text, you need to have another container, preferably Box to align Text component inside.
Create a simple composable function to show anything in center:
#Composable
fun Center(
modifier: Modifier = Modifier,
content: #Composable () -> Unit,
) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
content()
}
}
Call it from any other function:
#Composable
fun CenterAlignedText() {
Center(Modifier.fillMaxSize()) {
Text("Hello World!")
}
}

How do I create a Jetpack Compose Column where a middle child is scrollable but all of the other children are always visible?

I am creating a Jetpack Compose Dialog that contains a Column where all of the elements should always be visible, except for the third element, which is a Text that should be scrollable if the text doesn't fit the screen space. I almost achieved this with a secondary scrollable Column just for that Text element. However, this implementation pushes the bottom child (a button) out of view if there is a lot of text. Here is my code:
#Composable
fun WelcomeView(
viewModel: WelcomeViewModel,
onDismiss: () -> Unit
) {
Dialog(onDismissRequest = onDismiss) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(Spacing.extraLarge))
.background(Colors.backgroundBase)
.padding(all = Spacing.medium)
) {
Column {
IconView(
name = IconViewNames.RUNNING_SHOE,
size = IconViewSizes.LARGE,
color = Colors.primaryBase
)
Text(
viewModel.title,
style = Text.themeBillboard,
modifier = Modifier.padding(bottom = Spacing.medium)
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Text(
viewModel.message,
style = Text.themeHeadline,
modifier = Modifier.padding(bottom = Spacing.medium)
)
}
Button(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(Spacing.medium))
.background(Colors.primaryBase)
) {
Text(
"Continue",
style = Text.themeHeadline.copy(color = textExtraLight),
modifier = Modifier.padding(all = Spacing.extraSmall)
)
}
}
}
}
}
#Preview
#Composable
fun PreviewWelcomeView() {
WelcomeView(
viewModel = WelcomeViewModel(
firstName = "Wendy",
htmlWelcomeMessage = PreviewTextFixtures.threeParagraphs
),
onDismiss = {}
)
}
This is what it (correctly) looks like when there is only one paragraph of text:
But this is what it looks like when there are three paragraphs. The text scrolls correctly, but notice the missing "Continue" button:
Use this for your middle (scrollable) composable
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(weight =1f, fill = false)
) {
Text("Your text here")
}
The key is to use fill = false.
Just apply to the scrollable Column the weight modifier.
Something like:
Column (verticalArrangement= Arrangement.SpaceBetween) {
Text(
"viewModel.title",
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(1f, false)
) {
Text("...")
}
Button()
}

Jetpack Compose How to Align text or content of Text component with specific size to bottom left or right?

How can i align text to bottom section of a Text component with Jetpack Compose? TextAlign only has Start, End, Left, Center, Right and Justify options.
Text(
text = "First",
textAlign = TextAlign.Start,
modifier = Modifier
.background(Color(0xFF1976D2))
.size(200.dp),
color = Color.White,
)
I want to align Text component's content, each Text has a specific size using modifier.size(x), to align their text to bottom. In the image blue rectangles are Text with different sizes should align the text inside them like in classic Android done with android:gravity.
It is similar to textAlign = TextAlign.x but for bottom.
Setting alignment from a Box aligns Text inside Box or Modifier.align(Alignment.BottomEnd) in BoxScope does what android:layout_gravity does for views, aligns the Text component, not the content of Text component, you can see the difference here.
Code for the blue rectangles in the image is
#Composable
fun BoxExample() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.background(Color.LightGray)
) {
// This is the one at the bottom
Text(
text = "First",
modifier = Modifier
.background(Color(0xFF1976D2))
.size(200.dp),
color = Color.White,
)
// This is the one in the middle
Text(
text = "Second",
modifier = Modifier
.background(Color(0xFF2196F3))
.size(150.dp),
color = Color.White
)
// This is the one on top
Text(
text = "Third ",
modifier = Modifier
.background(Color(0xFF64B5F6))
.size(100.dp),
color = Color.White
)
}
}
For orange rectangles
#Composable
fun BoxShadowAndAlignmentExample() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.background(Color.LightGray)
.padding(8.dp)
) {
Box(
modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
) {
// This is the one at the bottom
Text(
text = "First",
modifier = Modifier
.background(Color(0xFFFFA000))
.size(200.dp),
color = Color.White
)
}
Box(
modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
.align(Alignment.TopEnd)
) {
// This is the one in the middle
Text(
text = "Second",
modifier = Modifier
.background(Color(0xFFFFC107))
.size(150.dp),
color = Color.White
)
}
val modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
.align(Alignment.BottomStart)
Box(
modifier = modifier
) {
// This is the one on top
Text(
text = "Third ",
modifier = Modifier
.background(Color(0xFFFFD54F))
.size(100.dp),
color = Color.White
)
}
}
}
Using align modifier you can align child components in specific positions relative to their parents:
Box(modifier = Modifier.fillMaxSize()) {
Text(modifier = Modifier.align(Alignment.BottomEnd),text = "Aligned to bottom end")
Text(modifier = Modifier.align(Alignment.BottomStart),text = "Aligned to bottom start")
Text(modifier = Modifier.align(Alignment.CenterStart),text = "Aligned to start center ")
Text(modifier = Modifier.align(Alignment.TopCenter),text = "Aligned to top center ")
}
Say your component is a Box, place your text within the Box like this:
Box(
modifier = Modifier.fillMaxSize(),
Alignment.BottomStart
) {
Text(
"First",
Modifier.padding(16.dp),
)
}
Basically, you define the section of the component that you want to use in that component, not in the text.
2 years later and I think I have the actual solution for this issue:
for your text composable, add this modifier:
.wrapContentHeight() and for the parameter, you may for example align it to the bottom with Alignment.Bottom
you can try with:
Text(
modifier = Modifier
.height(150.dp)
.wrapContentHeight(Alignment.Bottom)
,
text = "whooopla",
)

Categories

Resources