【问题标题】:Jetpack Compose list diffs animationJetpack Compose 列表差异动画
【发布时间】:2020-11-19 19:24:49
【问题描述】:

有没有办法在 Compose 中的列表(列/行)更改上获得动画效果,看起来类似于带有 setItemAnimator 的 recyclerview 动画?

【问题讨论】:

    标签: android-jetpack-compose


    【解决方案1】:

    目前无法使用LazyColumn/LazyRow 执行此操作。这是最终可能会添加的东西(尽管一如既往地预测未来:没有承诺),但目前它的优先级低于让更多基本功能正常工作。

    注意:我在实现这些组件的团队中工作。如果情况发生变化,我会更新这个答案。

    【讨论】:

    【解决方案2】:

    目前,您需要明确管理已更改项目的进入/退出转换。您可以使用AnimatedVisibility 来表示this example

    【讨论】:

    • AnimatedVisibility 的问题是如果你在ViewModel 中收集state,那么动画将被完全剪切。
    【解决方案3】:

    这是an example 至少用于处理项目添加/删除:

    
    @ExperimentalAnimationApi
    @Suppress("UpdateTransitionLabel", "TransitionPropertiesLabel")
    @SuppressLint("ComposableNaming", "UnusedTransitionTargetStateParameter")
    /**
     * @param state Use [updateAnimatedItemsState].
     */
    inline fun <T> LazyListScope.animatedItemsIndexed(
        state: List<AnimatedItem<T>>,
        enterTransition: EnterTransition = expandVertically(),
        exitTransition: ExitTransition = shrinkVertically(),
        noinline key: ((item: T) -> Any)? = null,
        crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
    ) {
        items(
            state.size,
            if (key != null) { keyIndex: Int -> key(state[keyIndex].item) } else null
        ) { index ->
    
            val item = state[index]
            val visibility = item.visibility
    
            androidx.compose.runtime.key(key?.invoke(item.item)) {
                AnimatedVisibility(
                    visibleState = visibility,
                    enter = enterTransition,
                    exit = exitTransition
                ) {
                    itemContent(index, item.item)
                }
            }
        }
    }
    
    @Composable
    fun <T> updateAnimatedItemsState(
        newList: List<T>
    ): State<List<AnimatedItem<T>>> {
    
        val state = remember { mutableStateOf(emptyList<AnimatedItem<T>>()) }
        LaunchedEffect(newList) {
            if (state.value == newList) {
                return@LaunchedEffect
            }
            val oldList = state.value.toList()
    
            val diffCb = object : DiffUtil.Callback() {
                override fun getOldListSize(): Int = oldList.size
                override fun getNewListSize(): Int = newList.size
                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                    oldList[oldItemPosition].item == newList[newItemPosition]
    
                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                    oldList[oldItemPosition].item == newList[newItemPosition]
    
            }
            val diffResult = calculateDiff(false, diffCb)
            val compositeList = oldList.toMutableList()
    
            diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
                override fun onInserted(position: Int, count: Int) {
                    for (i in 0 until count) {
                        val newItem = AnimatedItem(visibility = MutableTransitionState(false), newList[position + i])
                        newItem.visibility.targetState = true
                        compositeList.add(position + i, newItem)
                    }
                }
    
                override fun onRemoved(position: Int, count: Int) {
                    for (i in 0 until count) {
                        compositeList[position + i].visibility.targetState = false
                    }
                }
    
                override fun onMoved(fromPosition: Int, toPosition: Int) {
                    // not detecting moves.
                }
    
                override fun onChanged(position: Int, count: Int, payload: Any?) {
                    // irrelevant with compose.
                }
            })
            if (state.value != compositeList) {
                state.value = compositeList
            }
            val initialAnimation = Animatable(1.0f)
            initialAnimation.animateTo(0f)
            state.value = state.value.filter { it.visibility.targetState }
        }
    
        return state
    }
    
    data class AnimatedItem<T>(
        val visibility: MutableTransitionState<Boolean>,
        val item: T,
    ) {
    
        override fun hashCode(): Int {
            return item?.hashCode() ?: 0
        }
    
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false
    
            other as AnimatedItem<*>
    
            if (item != other.item) return false
    
            return true
        }
    }
    
    suspend fun calculateDiff(
        detectMoves: Boolean = true,
        diffCb: DiffUtil.Callback
    ): DiffUtil.DiffResult {
        return withContext(Dispatchers.Unconfined) {
            DiffUtil.calculateDiff(diffCb, detectMoves)
        }
    }
    

    【讨论】:

      【解决方案4】:

      名为 Modifier.animateItemPlacement() 的修改器 API 已实现并合并,可能会在即将发布的 Compose 版本中发布。推文:https://twitter.com/CatalinGhita4/status/1455500904690552836?s=20

      【讨论】:

      • 谢谢!不幸的是,这个 API 不支持插入/删除动画,只支持重新排列:twitter.com/and_kulikov/status/1455537110816075786。但是对于插入/删除,我们可以使用 Doris Liu 和 David Liu 建议的方法。希望将来会有更简洁的 API
      • Jetpack compose 在创建时牢记不变性。而不是调用 insert 来撰写你应该提供新列表并让其余部分进行撰写,它会自动执行必要的动画。
      • 这个 API 导致我的应用程序立即崩溃:stackoverflow.com/questions/70842611/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-22
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      • 1970-01-01
      相关资源
      最近更新 更多