I am building a basic layout which diplays a horizontally scrollable row with cards. The cards are a column component with a couple dividers in the middle:
#Composable
fun TestCard(
name: String
) {
Column(
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(12.dp),
horizontalAlignment = Alignment.Start
) {
Text(text = name, style = Typography.h4.bold.black)
Spacer(modifier = Modifier.height(8.dp))
Divider(color = Color.Black) //DOES NOT APPEAR IN ROW
Spacer(modifier = Modifier.height(8.dp))
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black) //DOES NOT APPEAR IN ROW
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Other text", style = Typography.h4.bold.black)
}
}
I also have a scrollable row that contains a list of these cards:
#Composable
fun TestComponent() {
val scrollState = rememberScrollState()
val names = listOf("Martha", "Erik","Steve","Roy","Pete")
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.primary),
horizontalAlignment = Alignment.Start
) {
Row(Modifier.horizontalScroll(scrollState), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
names.forEach { name ->
TestCard(name)
}
}
}
}
When I display the card on its own, I can see both dividers. When I display the row containing cards the dividers don't appear, because their width is set to 0 (I can verify with LayoutInspector the dividers are there). Visual representation:
I know I can create a state variable for width that would be updated inside the Modifier.onSizeChanged but I am sure there must be a reason for this to happen and a better solution.
The Divider has inside a fillMaxWidth() modifier, but as you check in the doc:
If the incoming maximum width is Constraints.Infinity this modifier will have no effect.
It happens because of the horizontalScroll.
You have to specify the width for the Dividers and the Spacers.
A workaround is to apply width(IntrinsicSize.Max) to the Column in the TestCard. In this way the Constraints have a Constraints.fixedWidth(width):
#Composable
fun TestCard(
name: String
) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.background(androidx.compose.material.MaterialTheme.colors.surface)
.padding(12.dp),
horizontalAlignment = Alignment.Start
) {
Text(text = name,)
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Divider(color = Color.Black, modifier = Modifier.fillMaxWidth())
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black)
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Text(text = "Other text" )
}
}
Related
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()
}
}
Hi I am using Jetpack Compose to create a Heterogenous list. I was successful in implementing it. My requirement is when I try to click an Item in the list, I need to recompose the list. The app crashes when tried to refresh the list with below exception:
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
My code is below:
#Composable fun PrepareOverViewScreen(overViewList: List<OverViewListItem>) {
Scaffold(topBar = { TopBar("OverView") },
content = { DisplayOverViewScreen(overViewList = overViewList) },
backgroundColor = Color(0xFFf2f2f2)
)
}
#Composable fun DisplayOverViewScreen(
modifier: Modifier = Modifier, overViewList: List<OverViewListItem>
) {
LazyColumn(modifier = modifier) {
items(overViewList) { data ->
when (data) {
is OverViewHeaderItem -> {
HeaderItem(data,overViewList)
}
}
}
}
}
HeaderItem Composable Function is below :
#Composable fun HeaderItem(overViewHeaderItem: OverViewHeaderItem,overViewList: List<OverViewListItem>) { <br>
var angle by remember {
mutableStateOf(0f)
}
var canDisplayChild by remember {
mutableStateOf(false)
}
**if(canDisplayChild){
HandleHistoryTodayChild(canDisplayChild = true,overViewList)
}**
when (overViewHeaderItem.listType) {
ItemType.IN_PROGRESS_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffdc8633)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = "In Progress", color = Color.White,
modifier = Modifier.padding(start = 16.dp)
)
}
}
ItemType.HISTORY_TODAY_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = stringResource(R.string.history_label), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
}
}
else -> {
Row(modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.history_yesterday), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(Modifier.weight(1f))
**Image(
painter = painterResource(id = R.drawable.ic_expand_more),
modifier = Modifier
.padding(end = 5.dp)
.rotate(angle)
.clickable {
angle = (angle + 180) % 360f
canDisplayChild = !canDisplayChild
},
contentDescription = "Expandable Image"
)**
}
}
}
}
Handle History info where recomposition is called
#Composable
fun HandleHistoryTodayChild(canDisplayChild:Boolean,overViewList: List<OverViewListItem>) {
if(canDisplayChild){
**PrepareOverViewScreen(overViewList = overViewList)**
}
}
Your problem should be:
Spacer(Modifier.weight(1f))
Try to specify a fixed height and work your solution from there.
I am trying to overlap two different compose elements. I want to show a toast kind of message at the top whenever there is an error message. I don't want to use a third party lib for such an easy use case. I plan to use the toast in every other composable screen for displaying error message. Below is the layout which i want to achieve
So I want to achieve the toast message saying "Invalid PIN, please try again".
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
and my screen composable is as follows
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box(){
MyToast(
title = "Invalid pin, please try again"
)
Column() {
//my other screen components
}
}
}
I will add the AnimatedVisibility modifier later to MyToast composable. First I need to overlap MyToast over all the other elements and somehow MyToast is just not visible
If you want the child of a Box to overlay/overlap its siblings behind, you should put it at the last part in the code
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
}
So if I put the green box before the bigger red box like this
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
}
the green box will hide behind the red one
You have to solutions, you can either put the Toast in the bottom of your code because order matters in compose:
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box() {
Column() {
//my other screen components
}
MyToast(
title = "Invalid pin, please try again"
)
}
}
}
Or you can keep it as it is, but add zIndex to the Toast:
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.zIndex(10f) // add z index here
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
Note: elevation in Card composable is not the same as elevation in XML so it's not going to make the composable in the top, it will just add a shadow but if you want to give the composable a higher z order use Modifier.zIndex(10f)
I've made a Row containing 2 different items but I want the item on the right to be positioned on the edge of the screen. I tried using weights with the weight modifiers (modifier = Modifier.weight(...f)) but the results are not accurate enough. I also couldn't find any horizontal alignment features.
In the attached screenshot there is still plenty of space to the right of the Switch that can be used to move it to the edge of the screen (on the right).
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "My custom switch", modifier = Modifier.weight(1f))
Switch(modifier = Modifier.weight(1f))
}
Using weight on first composable inside of Row:
var switchState by remember { mutableStateOf(value = false) }
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(weight = 1f),
text = "My switch"
)
Switch(
checked = switchState,
onCheckedChange = { switchState = it }
)
}
Using SpaceBetween on Row:
var switchState by remember { mutableStateOf(value = false) }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "My switch")
Switch(
checked = switchState,
onCheckedChange = { switchState = it }
)
}
You can apply a background to Text and Switch to see the difference involved in these two implementations.
With weight:
With SpaceBetween:
You could try something like:
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "My custom switch", modifier = Modifier.weight(1f))
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd){
Switch(checked = true, onCheckedChange = )
}
}
}
Maybe change the .fillmaxwidth of the modifier, but contentAlignment is what you are looking for I think.
I am looking to have a result like this. Two block with 2/3 size and 1/3 size respectively.
I am getting the expected result with this code.
#Preview(showBackground = true)
#Composable
fun LayoutCheck() {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
modifier = Modifier
.width(200.dp)
.weight(3f),
color = MaterialTheme.colors.primary
) {}
Surface(
modifier = Modifier
.width(200.dp)
.weight(1f),
color = MaterialTheme.colors.secondary
) {}
}
}
But when i put that inside a lazycolumn, nothing seems working. Not even getting a display.
#Preview(showBackground = true)
#Composable
fun LayoutCheck() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(20.dp)
) {
item {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
modifier = Modifier
.width(200.dp)
.weight(3f),
color = MaterialTheme.colors.primary
) {}
Surface(
modifier = Modifier
.width(200.dp)
.weight(1f),
color = MaterialTheme.colors.secondary
) {}
}
}
}
}
}
If i remove t he weight and put some height,it works. But i don't want to hardcode there. So how to make it work with weight. Expecting some help..
Thanks
NB: I want scroll functionality, that's why going with LazyColumn
Real World scenario :
A Login Screen, with Logo at the first 2/3 portion and a Text and Button at the bottom 1/3 portion
Scenario 1 : Working Perfectly:
Scenario 2: If users font is bigger or screen is rotated, they wont be able to see the button
Since you have only one item just use a Column with a verticalScroll:
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally,
) {
//...
}
Also if you want 2/3 and 1/3, use in the 1st Surface the weight(2f) modififer.
You prevent the text from being resized by applying the weight modifier. Instead, I suggest that you make the element spacing flexible, like this:
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.my_image),
contentDescription = "",
)
Spacer(Modifier.weight(1.35f))
Text(
"Your app is being reviewed",
)
Spacer(Modifier.weight(0.2f))
Button(onClick = { /*TODO*/ }) {
Text("Log out")
}
Spacer(Modifier.weight(0.2f))
}
Spacers explanation: