I have an HorizontalPager in Compose and users are able to remove any item of Pager. When the user removes any item i want other elements move to empty place with an animation.
This is actually a default animation for RecylerView(ListAdapter) or even LazyColumn uses animateItem to manage that or with deprecated name animateItemPlacement.
I'm wondering how can I achieve the same concept in HorizontalPager?
@Composable
fun HomePageBanner(
bannerList: List<BannerUIModel>,
modifier: Modifier = Modifier,
onButtonClicked: (index: Int) -> Unit = {},
onItemRemoved: (index: Int) -> Unit = {},
) {
val pageCount = bannerList.size
val pagerState = rememberPagerState(pageCount = { pageCount })
HorizontalPager(
state = pagerState,
modifier = modifier,
contentPadding = PaddingValues(start = 16.dp, end = 12.dp),
pageSpacing = 0.dp,
) { index ->
val banner = bannerList[index]
BannerCardItem(
bannerItem = banner,
modifier = Modifier.fillMaxWidth().padding(12.dp),
onButtonClicked = { onButtonClicked(index) },
onItemRemoved = {
// When this is called, the item will be removed
// and HomePageBanner will be called with the new list
onItemRemoved(index)
},
)
}
}
I have an HorizontalPager in Compose and users are able to remove any item of Pager. When the user removes any item i want other elements move to empty place with an animation.
This is actually a default animation for RecylerView(ListAdapter) or even LazyColumn uses animateItem to manage that or with deprecated name animateItemPlacement.
I'm wondering how can I achieve the same concept in HorizontalPager?
@Composable
fun HomePageBanner(
bannerList: List<BannerUIModel>,
modifier: Modifier = Modifier,
onButtonClicked: (index: Int) -> Unit = {},
onItemRemoved: (index: Int) -> Unit = {},
) {
val pageCount = bannerList.size
val pagerState = rememberPagerState(pageCount = { pageCount })
HorizontalPager(
state = pagerState,
modifier = modifier,
contentPadding = PaddingValues(start = 16.dp, end = 12.dp),
pageSpacing = 0.dp,
) { index ->
val banner = bannerList[index]
BannerCardItem(
bannerItem = banner,
modifier = Modifier.fillMaxWidth().padding(12.dp),
onButtonClicked = { onButtonClicked(index) },
onItemRemoved = {
// When this is called, the item will be removed
// and HomePageBanner will be called with the new list
onItemRemoved(index)
},
)
}
}
Share
edited Nov 29, 2024 at 17:51
Umut Tekin
asked Nov 29, 2024 at 14:23
Umut TekinUmut Tekin
2582 silver badges17 bronze badges
2
- Please edit your question and provide the code you have so far. – tyg Commented Nov 29, 2024 at 14:29
- Actually this question is independent of code because I am looking for the equivalent of the default animation used in Lists in the Android world for Compose Pager. I'll still share the basic implementation of the Pager. – Umut Tekin Commented Nov 29, 2024 at 17:47
1 Answer
Reset to default 4Modifier.animateItem()
is a scoped Modifier defined in LazyItemScope
. There is no default PagerScope modifier, actually nothing in PagerScope yet. You can open a feature request for Pager as well.
/**
* Receiver scope for [Pager].
* Note: This is empty now, but we may add new members in the future if needed.
*/
sealed interface PagerScope
internal object PagerScopeImpl : PagerScope
But You can apply snapping behavior to LazyRow/Column
you can snap as Pager
does and use Modifer.animateItem()
Animating LazyRow item change
Data class must have unique keys for matching which items to animate for placement, which ones for appear and which ones for disappear and must be used inside key = {item-> item.id}
.
data class MyData(val id: String = UUID.randomUUID().toString(), val value: Int)
class MyViewModel : ViewModel() {
val list =
mutableStateListOf<MyData>().apply {
repeat(6) {
add(
MyData(value = it)
)
}
}
fun removeItem(index: Int) {
list.removeAt(index)
}
}
@Preview
@Composable
fun LazyRowSnapAndDeleteAnimation() {
val viewModel = remember {
MyViewModel()
}
val lazyListState = rememberLazyListState()
Column(
modifier = Modifier.fillMaxSize().background(backgroundColor).padding(vertical = 32.dp)
) {
val list = viewModel.list
LazyRow(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(start = 16.dp, end = 12.dp),
flingBehavior = rememberSnapFlingBehavior(lazyListState, snapPosition = SnapPosition.Start),
state = lazyListState
) {
itemsIndexed(
items = list,
key = { _, item ->
item.id
}
) { page, item ->
Column(
modifier = Modifier
.animateItem()
.fillParentMaxWidth()
.height(200.dp)
.shadow(2.dp, RoundedCornerShape(16.dp))
.background(Color.White)
.padding(32.dp)
) {
Text("Item")
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
viewModel.removeItem(page)
}
) {
Text("Remove ${item.value}")
}
}
}
}
}
}
Animating HorizontalPager item change
If using LazyRow is out of question you can write a Modifier that does that but it's not easy and there are several cases to handle since we are building similar Modifier
as animateItem
manually.
Basically, you need to set Modifier.layout
with layer
, also HorizontalPager has fixed page size, do not let items shrink you need to handle item scrolling. In addition, in your case, you should also handle cases for previous and next items because of content padding makes them partially visible.
Step1 - Using a flag to signal animation
When manually animating deleted items first step is using a flag to make them eligible for animation then after animation finishes because removin items list removes them from composition, so they must be removed at the end.
Or you can use another list to compare as LazyList does to check which items to change position, which items to disappear and which ones to appear.
Updated ViewModel and data like this
data class MyData(val id: String = UUID.randomUUID().toString(), val value: Int, val isAlive: Boolean = true)
class MyViewModel : ViewModel() {
val list =
mutableStateListOf<MyData>().apply {
repeat(6) {
add(
MyData(value = it)
)
}
}
fun updateStatus(index: Int) {
val newItem = list[index].copy(isAlive = false)
list[index] = newItem
println("Update Status: $index")
}
fun removeItem(index: Int) {
println("