首页 > 解决方案 > 长按回收站查看项目动画

问题描述

我需要通过长按recyclerview中的圆角矩形来剪辑所有项目,并在每个项目视图的两个极端中显示两个视图以选择和重新排列项目。如何做到这一点?我正在使用带有由 roomdb 支持的 RemoteMediator 的 Paging 3 库来显示项目。

长按:

  1. 将所有项目向左平移 ->
  2. 为形状蒙版设置动画(不缩放项目,但应用圆角矩形蒙版(或剪辑)以减少显示)并应用渐变叠加以显示编辑模式
  3. 每个项目视图两侧的两个视图的动画外观(显示)
    在此处输入图像描述

标签: androidandroid-recyclerviewandroid-animation

解决方案


最后,我最终创建了单独的 Animator 对象并使用 AnimatorSet 一起播放它们。

RecyclerViewItemAnimatorSet 类用于获取所有视图持有者并应用在其构造函数中收到的动画师

import android.animation.Animator
import android.animation.AnimatorSet
import androidx.core.animation.doOnEnd
import androidx.recyclerview.widget.RecyclerView
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean

/**
 * @author sundara.santhanam
 * @since 09-09-2021
 */
class RecyclerViewItemAnimatorSet<in VH : RecyclerView.ViewHolder>(
    private val recyclerView: RecyclerView,
    private val animatorProviders: List<AnimatorProvider<VH>>,
    private val duration: Long,
    private val afterAnim: (SelectionModeAnimationState) -> Unit
) {
    private val forwardPlayInProgress = AtomicBoolean(false)
    private val reversePlayInProgress = AtomicBoolean(false)


    fun playTogether() {
        if (reversePlayInProgress.get().not()) {
            forwardPlayInProgress.set(true)
            playAnimation({ viewHolder ->
                animatorProviders.map { it.getForwardAnimator(viewHolder as VH) }
            }) {
                forwardPlayInProgress.set(false)
                theEnd(true)
            }
        }
    }

    private fun theEnd(selectionMode: Boolean) {
        var selectionModeAnimationState = SelectionModeAnimationState(selectionMode = selectionMode)
        animatorProviders.forEach {
            selectionModeAnimationState = it.mutateWithFinalValue(selectionModeAnimationState)
        }
        Timber.d("SelectionModeAnimationState: %s", selectionModeAnimationState)
        afterAnim(selectionModeAnimationState)
    }

    fun reversePlayTogether() {
        if (forwardPlayInProgress.get().not()) {
            reversePlayInProgress.set(true)
            playAnimation({ viewHolder -> animatorProviders.map { it.getReverseAnimator(viewHolder as VH) } }) {
                reversePlayInProgress.set(false)
                theEnd(false)
            }
        }
    }

    private fun playAnimation(
        animatorFetcher: (RecyclerView.ViewHolder) -> List<Animator>,
        performAtEnd: () -> Unit
    ) {
        for (index in recyclerView.visibleRange()) {
            recyclerView.findViewHolderForAdapterPosition(index)?.let { viewHolder ->
                val animatorSet = AnimatorSet()
                animatorSet.playTogether(animatorFetcher(viewHolder))
                animatorSet.duration = duration
                animatorSet.start()
                animatorSet.doOnEnd {
                    performAtEnd.invoke()
                }
            }
        }
    }
}

AnimatorProvider 合约:


/**
 * @author sundara.santhanam
 * @since 09-09-2021
 */
abstract class AnimatorProvider<in VH : RecyclerView.ViewHolder> {
    private var prevMode = 0
    fun getForwardAnimator(viewHolder: VH): Animator {
        val mode = 1
        if (prevMode != mode) {
            prevMode = mode
            onModeChange(mode, viewHolder)
        }
        return getAnimator(viewHolder, mode)
    }

    fun getReverseAnimator(viewHolder: VH): Animator {
        val mode = -1
        if (prevMode != mode) {
            prevMode = mode
            onModeChange(mode, viewHolder)
        }
        return getAnimator(viewHolder, mode)
    }

    abstract fun mutateWithFinalValue(selectionModeAnimationState: SelectionModeAnimationState): SelectionModeAnimationState
    val defaultValueAnimator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)

    // @param mode =1 is forward animation mode and -1 is reverse animation mode
    abstract fun onModeChange(
        mode: Int,
        viewHolder: VH
    )

    abstract fun getAnimator(viewHolder: VH, mode: Int): Animator
}

动画提供者的示例:OutlineProvider animator

/**
 * @author sundara.santhanam
 * @since 09-09-2021
 */
class OutLineAnimatorProvider : AnimatorProvider<FavoritesViewDelegate.FavViewHolder>() {
    private var finalRight: Int = -1
    private var initRight: Int = -1
    private var initHeight: Int = -1
    private var diff = 0f

    override fun getAnimator(
        viewHolder: FavoritesViewDelegate.FavViewHolder,
        mode: Int
    ): Animator {
        Timber.d(
            "%s initRight:%d finalRight:%s", (if (mode == 1) {
                "reverse"
            } else {
                "forward"
            }), initRight, finalRight
        )
        defaultValueAnimator.addUpdateListener {
            viewHolder.binding.itemContainer.updateRoundedCornersOutlineProvider(
                (initRight - mode * diff * (it.animatedValue as Float)).toInt(), initHeight
            )
            viewHolder.binding.itemContainer.requestLayout()
        }
        return defaultValueAnimator
    }

    override fun onModeChange(
        mode: Int,
        viewHolder: FavoritesViewDelegate.FavViewHolder
    ) {
        val prevInit = initRight
        initRight = if (finalRight == -1) {
            val bounds = Rect()
            viewHolder.binding.itemContainer.getDrawingRect(bounds)
            initHeight = bounds.height()
            diff = bounds.width() * FINAL_RIGHT_FACTOR
            Timber.d("initRight=%d width=%d diff=%f", bounds.right, bounds.width(), diff)
            bounds.right
        } else {
            finalRight
        }
        finalRight =
            if (prevInit != -1) {
                prevInit
            } else {
                (initRight - mode * diff).toInt()
            }
    }

    override fun mutateWithFinalValue(selectionModeAnimationState: SelectionModeAnimationState) =
        selectionModeAnimationState.copy(
            finalWidth = finalRight,
            initHeight = initHeight
        )

    companion object {
        const val FINAL_RIGHT_FACTOR = 0.2f
    }
}

圆角轮廓提供程序:


/**
 * @author sundara.santhanam
 * @since 09-09-2021
 */
data class RoundedCornersOutlineProvider(
    val radius: Float? = null,
    val width: Int? = null,
    val height: Int? = null
) : ViewOutlineProvider() {

    override fun getOutline(view: View, outline: Outline) {
        val left = 0
        val top = 0
        val right = width ?: view.width
        val bottom = height ?: view.height

        if (radius != null) {
            val cornerRadius = radius
            outline.setRoundRect(left, top, right, bottom, cornerRadius)
        }
    }
}

fun View.updateRoundedCornersOutlineProvider(width: Int, height: Int) {

    outlineProvider = try {
        (outlineProvider as RoundedCornersOutlineProvider).copy(
            width = width,
            height = height
        )
    } catch (e: Exception) {
        RoundedCornersOutlineProvider(
            width = width,
            height = height
        )
    }
}

fun View.setRoundedCornerOutlineProvider(radiusDp: Float) {
    Utils.init(context)
    val radius = convertDpToPixel(radiusDp)
    val bounds = Rect()
    getDrawingRect(bounds)
    outlineProvider =
        RoundedCornersOutlineProvider(radius)
    clipToOutline = true
}

fun View.getOutlineRight(): Int {
    val bounds = Rect()
    getDrawingRect(bounds)
    return bounds.right
}

在回收站视图中的用法:


    private fun setupRecyclerView() {
        favoriteViewAnimatorSet = RecyclerViewItemAnimatorSet(
            binding.rvWishList, listOf(
                OutLineAnimatorProvider(),
                TranslationAnimatorProvider(),
                AlphaAnimatorProvider()
            ), 500L
        ) { selectionMode ->
            favoritesViewDelegate.selectionModeAnimationState.set(selectionMode)
            binding.rvWishList.adapter?.run {
                repeat(itemCount) { index ->
                    if (index !in binding.rvWishList.visibleRange()) {
                        notifyItemChanged(index)
                                            }
                }
            }
            binding.rvWishList.enableScroll(recyclerTouchDisabler, recyclerTouchListener)
        }
        binding.rvWishList.adapter =
            FavoritesAdapter(getListOfDelegates())
        recyclerTouchListener = RecyclerTouchListener(
            context, binding.rvWishList,
            clickListener = object : ClickListener {
                override fun onClick(view: View?, position: Int) {
                }

                override fun onLongClick(view: View?, position: Int) {
                    favoritesViewDelegate.selectionModeAnimationState.set(
                        selectionMode().copy(
                            selectionMode = !selectionMode().selectionMode
                        )
                    )
                    if (selectionMode().selectionMode) {
                        recyclerTouchDisabler =
                            binding.rvWishList.disableScrollAndGetDisabler(recyclerTouchListener)
                        favoriteViewAnimatorSet.playTogether()
                    } else {
                        recyclerTouchDisabler =
                            binding.rvWishList.disableScrollAndGetDisabler(recyclerTouchListener)
                        favoriteViewAnimatorSet.reversePlayTogether()
                    }
                }
            })

        binding.rvWishList.addOnItemTouchListener(recyclerTouchListener)
    }

推荐阅读