Lazy layouts contentPadding
May 22, 2022
TL;DR: When working with Lazy layouts in Compose, use contentPadding
to avoid clipping before and after the last item in your list. The concept is similar to clipToPadding = true
in RecyclerView.
Here is a LazyRow
implementation that has this issue (notice that Modifier.padding
was used):
LazyRow( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier .padding( start = 24.dp, end = 24.dp ) )
The problem is both start
and end
padding is always there for every item. What we really want is add add start
and end
padding for the first item and the last item. Items in between should not be clipped when scrolled.
It’s easy to make the same mistake with a LazyColumn
:
Again, the top
and bottom
padding that’s always there for every item. Ideally, only the first and last items in our LazyColumn
would have a padding.
Here is the full source code for the LazyRow
example above:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { LazyLayoutsDemoTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier .fillMaxSize(), color = MaterialTheme.colors.background ) { Box( modifier = Modifier .fillMaxWidth() .wrapContentSize(Alignment.Center) ) { MyCardList() } } } } } } @Composable fun MyCardList() { val cardData = remember { generateFakeCards() } LazyRow( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier .padding( start = 24.dp, end = 24.dp ) ) { items(cardData) { item -> MyCard( title = item.first, subtitle = item.second ) } } } @Composable fun MyCard( title: String, subtitle: String ) { Card( shape = RoundedCornerShape(12.dp), modifier = Modifier .height(180.dp) .width(140.dp) ) { Image( painter = painterResource(id = R.drawable.green_card), contentDescription = null, contentScale = ContentScale.FillBounds ) Column( verticalArrangement = Arrangement.SpaceBetween, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding( top = 48.dp, bottom = 16.dp ) ) { Text( text = title, color = Color.White, style = MaterialTheme.typography.h6, textAlign = TextAlign.Center ) Text( text = subtitle, color = Color.White, style = MaterialTheme.typography.subtitle1, textAlign = TextAlign.Center ) } } } @Preview(showBackground = true) @Composable fun MyCardPreview() { LazyLayoutsDemoTheme { MyCard( title = "my title", subtitle = "my subtitle" ) } } private fun generateFakeCards(): List<Pair<String, String>> { return MutableList(10) { index -> val cardNumber = index+1 "Title $cardNumber" to "Subtitle $cardNumber" } }
The issue is caused by using the modifier
on the LazyRow
:
LazyRow( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier .padding( start = 24.dp, end = 24.dp ) )
Using that modifier
clips your content to remain within the bounds of your LazyRow
padding.
Without that modifier
, the layout does not look correct either. We do want the padding to be applied before the first and after the last item without clipping the content:
This is what contentPadding
param is designed to address:
To do that, we removed the modifier
and used contentPadding
instead:
LazyRow( horizontalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues( start = 24.dp, end = 24.dp ) ) { items(cardData) { item -> MyCard( title = item.first, subtitle = item.second ) } }
The contentPadding
parameter is very well documented as well:
@param contentPadding a padding around the whole content. This will add padding for the content after it has been clipped, which is not possible via [modifier] param. You can use it to add a padding before the first item or after the last one. If you want to add a spacing between each item use [horizontalArrangement].
That was easy! The same fix applies to LazyColumn
LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues( top = 24.dp, bottom = 24.dp ) ) { items(cardData) { item -> MyCard( title = item.first, subtitle = item.second ) } }