长按回收站查看项目动画

Posted

技术标签:

【中文标题】长按回收站查看项目动画【英文标题】:Recycler view item animation on long press 【发布时间】:2021-11-05 05:13:43 【问题描述】:

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

长按:

    动画将所有项目向左平移 -> 为形状遮罩制作动画(不缩放项目,但应用圆角矩形遮罩(或剪辑)以减少显示)并应用渐变叠加以显示编辑模式 每个项目视图两侧的两个视图的动画外观(显示)

【问题讨论】:

【参考方案1】:

最后,我创建了单独的 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 动画师

/**
 * @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

在recyclerview中的使用:


    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)
    

【讨论】:

以上是关于长按回收站查看项目动画的主要内容,如果未能解决你的问题,请参考以下文章

处理项目长按回收站视图

如何在回收站视图android中选择多个项目?

过渡动画无法从回收视图到详细信息屏幕

linux exit回收资源 卡死

显示更多/查看回收站视图中的所有项目 [关闭]

如何在回收站视图上启用和禁用拖放