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
)
}
}