Compose LazyList section background
Asked Answered
I

5

16

I'm having some trouble figuring out if my problem is a Jetpack Compose missing feature or if I can't find how it is done.

Let's say I want to make this page

enter image description here

It needs to be scrollable, because the content is long.

I also would like to use a lazy column to load the list of users that is shown in the image.

The issue is that you can't have a LazyColumn inside a vertically scrollable layout, so i thought i would simply make the whole page a LazyColumn. Now there is another issue, i want the user list to have a box around it with the background color and rounded borders as shown, but you can't put a box around the LazyListScope.items() and if you load the list as a single composable like item { UserList() } then it just makes it a column, losing the lazy part.

How would one do this?

Incestuous answered 29/6, 2021 at 16:20 Comment(3)
Did you ever find a solution for this?Timmy
Unfortunately noIncestuous
when I had a similar problem I made the inside list collapsable, if the user wants to view the list it expanded, and at that time scroll from the parent is removed, so the inner lazy column won't trow infinite height. after collapse scroll is returned to the parentXeres
P
2

I have achieved this behaviour with a bit of a hack:

  1. Use a Card for every item
  2. The first card must have RoundedCornerShape with topStart and topEnd radius
  3. The first card must have RoundedCornerShape with bottomStart and bottomEnd radius
  4. All Other Cards are of RectangleShape
  5. (Special case: If there is only one item use a regular RoundedCornerShape)
private val topShape = RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp)
private val bottomShape = RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp)

@Composable
fun LazyColumnCategory(
    itemCount: Int,
    idx: Int,
    content: @Composable (Shape) -> Unit,
) {
    Column {
        content(
            if (itemCount == 1)
                RoundedCornerShape(4.dp)
            else if (idx == 0)
                topShape
            else if (idx == itemCount - 1)
                bottomShape
            else
                RectangleShape
        )
        if (idx != itemCount - 1)
            Divider()
    }
}

Use this within your LazyColumn the following way:

LazyColumn {
    itemsIndexed(yourList) { idx, data ->
        LazyColumnCategory(itemCount = yourList.size, idx = idx) { shape ->
            Card(shape = shape) {
                // your content here
            }
        }
    }

}

Be careful when applying padding to your cards. Always define the padding before the height, otherwise there will be gaps. (see the docs)

Polysemy answered 9/6, 2022 at 22:58 Comment(0)
C
0

If i understood the problem correctly, what you need to do is to define separately the layout of the "item" you represent in your LazyColumn. Lets say as an example you have some lectures to present in your list. The way you can define this is:

LazyColumn(
   //modifiers etc
) {
    items(
        items = **lectures** -> your list of items,
        itemContent = {
            **LectureListItem**(it) -> your specified layout
        }
    )
}

And underneath you create your composable LectureListItem which has the layout you want(wheather it is a box, column, row and all the things in it). Example:

@Composable
fun LectureListItem(
    lecture: Lecture
) {
    Box(
        modifier = Modifier
            .padding(8.dp)
            .background(//exampleColor)
    ) {
        //Other elements of your layout
    }
}
Credits answered 29/6, 2021 at 17:26 Comment(4)
Actually no, this isn't the problem, the problem is that i want to have a background behind a group of those items, that isn't inside the item itself, kinda like if you had a background modifier on the "items" methodIncestuous
Well then you could just add .background() to your modifier in LazyColumn: LazyColumn( modifier = Modifier.padding(top = 8.dp).background(Color.Black) ) {//items, etc}Intercourse
No because i have multiple "items" calls inside LazyColumn, and i need a background only around some of them, i would need a background modifier on the "items" call, but it isn't an optionIncestuous
If you look at my image, the whole page is inside a LazyColumn, one "item" call is for the first group of cards, another for the second, another for the third, and then i need an "items" call for the user list, and around this list of users i need a background, like in the picture.Incestuous
B
0

This assumes that you know the indices of the particular items you want to encase in a box.

val list = ... //Contains all the items
val lowerBound = ... // Start of User List
val upperBound = ...//"
LazyColumn{
    list.forEachIndexed{index, item ->
          if(lowerBound == index){
              item{
                Text(Modifier.clip(RoundedCornerShape(topStartPercent = 50, topEndPercent = 50)) //User
.background (color)
)
              }
else if(upperBound == index){
Text(Modifier.clip(RoundedCornerShape(bottomStartPercent = 50, bottomEndPercent = 50)) //User
.background (color)
}
else if(lowerBound < index < upperBound){
Text(Modifier.background(color)
else{
//Regular List Items
}
          }
    }
}

This is only a workaround. This will give the exact effect, but is not really a box.

A cleaner solution would be to actually construct a custom Composable and upon detection of the lowerBound, add that Composable as a whole item, because although you can't use items inside a box, you can use vice versa.

Here's how:-

val list = ... //Contains all the items
val lowerBound = ... // Start of User List
val upperBound = ...//"
LazyColumn{
var x = 0
while(x++ != list.size()){
   if(index == lowerBound){
       item{
         UserList(list.subList(lowerBound, upperBound))
       }
   }
   else if(lowerBound < index < upperBound){
   continue
   }
   else{
     item{
        User()
     }
    }
}
}

@Composable
fun UserList(list){
  LazyColumn{
    //Display List
  }
}

This keeps the column lazy. No performance impacts

Buzzer answered 29/6, 2021 at 17:54 Comment(4)
Thanks, i thought about it, but i don't want a background for each user, because as you said it is a workaround, i want a single background behind all the user items, as if it was a LazyColumn with modifier background inside the main LazyColumnIncestuous
Refer to the "cleaner solution"Buzzer
Yes but if you put a custom composable inside an items call, you lose the Lazy part inside that composable, the only part where i could actually need it because it's the only one where i don't control the number of itemsIncestuous
That would crash your application with: "Nesting scrollable in the same direction layouts like LazyColumn and Column(Modifier.verticalScroll()) is not allowed"Incestuous
C
0

If you want to have a background on group of items, you can set background only on the items of that group.

The trick is to set the background before your other modifiers, so it's applied before item is styled, like this:

ListItem(modifier = Modifier
    .background(GrayBackground)
    .padding(horizontal = 20.dp)
)
Carbonization answered 15/7, 2021 at 12:33 Comment(1)
This would add the background in the single item, not on the item groupIncestuous
D
0

As of 5/16/2022 the best way to add a uniformed background to a list of items is through a custom composable that nests a *listOf<Any //your data class/>() within a LazyColumn() - "item { }" block.

Below is an example of a function that extends the scope of a LazyColumn()

fun LazyListScope.itemGroup(list: List<Any>) {

   return this.item {
       //**Some composable you want to display*/
   }

}

within the return block of the function above, include a Column()

Column(
    modifier = Modifier
        .clip(RoundedCornerShape(5))
        .background(DarkShade.copy(0.15f))
        .padding(horizontal = padding.medium, vertical = padding.small),
    verticalArrangement = Arrangement.spacedBy(padding.small)
) { 
  
  //**your list of items*/

}

This Column() allows you to style your background in anyway you'd like; color, padding, shape and more. Once you have your preferred background you can instantiate your list items with a for loop.

list.forEach { item ->
    ListItem(
        icon = item.icon,
        title = item.title,
        subtitle = item.subtitle,
        onClick = item.onClick
    )
}

Finally you can simply call itemGroup() in any LazyColumn() to use

LazyColumn {

   itemGroup(emptyList())

}
Deed answered 16/5, 2022 at 17:23 Comment(2)
Hi, thank you for the answer, I have a question about this implementation. Wouldn’t this keep the whole list of items loaded even if only one is visible on screen? Basically making it a List and not a LazyList?Incestuous
That's a great question. My initial answer is that the contents of the itemGroup aren't lazy loaded, but because it's wrapped in a lazyColumn each itemGroup will be lazy loaded. In short making more itemGroups rather than adding all your items to one itemGroup will solve this obstacle.Deed

© 2022 - 2024 — McMap. All rights reserved.