最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - Implementing change order (drag and drop) with animations for items (cards) in LazyColumn similar to AppleWallet? - St

programmeradmin1浏览0评论

Here's the example how it works in AppleWallet:

Basically the move animation happens like this: first the card smoothly moves down and then up

So I have made Jetpack Compose sample example (full code) with this reorderable library (edited a couple of code lines), but for now it uses default Modifier.animateItem() animation for items:


private val CARD_HEIGHT = 500.dp

@Composable
fun ScreenContent(modifier: Modifier = Modifier) {
    var items by remember { mutableStateOf((0..5).map { getRandomColor() }) }

    val lazyListState = rememberLazyListState()

    val cardPctHeight = 0.1f
    val cardLastPctHeight = 0.5f

    // 
    val reorderableLazyListState = rememberReorderableLazyListState(
        lazyListState = lazyListState,
        shouldItemMoveThreshold = (CARD_HEIGHT * cardPctHeight) / 2f
    ) { from, to ->
        items = items.toMutableList().apply {
            add(to.index, removeAt(from.index))
        }
    }

    LazyColumn(
        state = lazyListState,
        modifier = modifier.fillMaxSize(),
        contentPadding = PaddingValues(top = 10.dp),
        verticalArrangement = Arrangement.Bottom
    ) {
        itemsIndexed(
            items = items,
            key = { _, item -> item.toArgb() }
        ) { index, item ->
            ReorderableItem(reorderableLazyListState, key = item.toArgb()) {
                Box(
                    modifier = Modifier
                        .padding(horizontal = 10.dp)
                        .draggableHandle()
                        .layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            val placeableHeight = if (index != items.lastIndex) {
                                placeable.height * cardPctHeight
                            } else {
                                placeable.height * cardLastPctHeight
                            }
                            layout(placeable.width, placeableHeight.roundToInt()) {
                                placeable.place(0, 0)
                            }
                        }
                        .fillMaxWidth()
                        .clip(RoundedCornerShape(12.dp))
                        .height(CARD_HEIGHT)
                        .background(color = item)
                ) {
                    //
                }
            }
        }
    }
}

private fun getRandomColor(): Color {
    return Color(
        red = Random.nextFloat(),
        green = Random.nextFloat(),
        blue = Random.nextFloat(),
        alpha = 1f
    )
}

Right now it works like this with the default animation:

That reorderable library specifies default animation here

@Composable
fun LazyItemScope.ReorderableItem(
    ...
    animateItemModifier: Modifier = Modifier.animateItem(),

UPDATE:

I was able to get the same animation (with exit and enter behavior) using AnimatedVisibility (instead of LazyItemScope.animateItem) but it only works normally if we dragging the item slowly, because there are Coroutine delays for the animation switching (enter/exit), and also if we move the dragging item faster we can see that other items are clipped to their visible height when they are moved.

Video:

Code:

private val CARD_HEIGHT = 500.dp

@Composable
fun AnimatedReorderableItem(
    visible: Boolean,
    content: @Composable () -> Unit
) {
    AnimatedVisibility(
        visible = visible,
        enter = slideInVertically(
            // The item slides in from below
            initialOffsetY = { fullHeight -> fullHeight }
        ) + fadeIn(animationSpec = tween(durationMillis = 300)),
        exit = slideOutVertically(
            // The item slides out downward
            targetOffsetY = { fullHeight -> fullHeight }
        ) + fadeOut(animationSpec = tween(durationMillis = 300))
    ) {
        content()
    }
}

@Composable
fun ScreenContent(modifier: Modifier = Modifier) {
    Column {
        var items by remember { mutableStateOf((0..5).map { getRandomColor() }) }

        val itemVisibility = remember { mutableStateMapOf<Color, Boolean>() }

        val lazyListState = rememberLazyListState()

        val cardPctHeight = 0.1f
        val cardLastPctHeight = 0.5f

        // 
        val reorderableLazyListState = rememberReorderableLazyListState(
            lazyListState = lazyListState,
            shouldItemMoveThreshold = (CARD_HEIGHT * cardPctHeight) / 2f
        ) { from, to ->
            val item = items[to.index]

            itemVisibility[item] = false

            // TODO: but the problem here, we can't move the dragging item too fast (before animation ends)
            delay(300)

            itemVisibility[item] = true

            items = items.toMutableList().apply {
                add(to.index, removeAt(from.index))
            }
        }

        LazyColumn(
            state = lazyListState,
            modifier = modifier
                .fillMaxWidth()
                .weight(1f),
            contentPadding = PaddingValues(top = 10.dp),
            verticalArrangement = Arrangement.Bottom
        ) {
            itemsIndexed(
                items = items,
                key = { _, item -> item.toArgb() }
            ) { index, item ->
                AnimatedReorderableItem(visible = itemVisibility[item] ?: true) {
                    ReorderableItem(
                        state = reorderableLazyListState,
                        key = item.toArgb(),
                        animateItemModifier = Modifier // disable default animation Modifier.animateItem()
                    ) {
                        Box(
                            modifier = Modifier
                                .padding(horizontal = 10.dp)
                                .longPressDraggableHandle()
                                .layout { measurable, constraints ->
                                    val placeable = measurable.measure(constraints)
                                    val placeableHeight = if (index != items.lastIndex) {
                                        placeable.height * cardPctHeight
                                    } else {
                                        placeable.height * cardLastPctHeight
                                    }
                                    layout(placeable.width, placeableHeight.roundToInt()) {
                                        placeable.place(0, 0)
                                    }
                                }
                                .fillMaxWidth()
                                .clip(RoundedCornerShape(12.dp))
                                .height(CARD_HEIGHT)
                                .background(color = item)
                        ) {
                            //
                        }
                    }
                }
            }
        }
    }
}

As I understand the Modifier.animateItem() is limited and only allows one transition animation for placement, we can't just set animation for item enter/exit as with AnimatedVisiblity.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论