长按回收站查看项目动画
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)
【讨论】:
以上是关于长按回收站查看项目动画的主要内容,如果未能解决你的问题,请参考以下文章