android - 长按回收站查看项目动画
问题描述
我需要通过长按recyclerview中的圆角矩形来剪辑所有项目,并在每个项目视图的两个极端中显示两个视图以选择和重新排列项目。如何做到这一点?我正在使用带有由 roomdb 支持的 RemoteMediator 的 Paging 3 库来显示项目。
长按:
解决方案
最后,我最终创建了单独的 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)
}
推荐阅读
- amazon-cloudwatch - 记录 Amazon Cloudwatch 上异步作业的成功/失败指标
- javascript - Vuejs处理多个复选框单击
- jquery - jquery替换一个数字的一个数字
- python - 通过合并多个 Pandas 数据框解决错误
- h2o - H2OAutoML 是否处理超参数优化?
- xslt - 不使用 not 选择 xslt 中的特定节点子元素
- sql-server - SSRS 调用存储过程失败,找不到用户 'dbo'
- javascript - POST https://localhost:44351/Common/SearchAvailableTechList 500(内部服务器错误)
- javascript - 根据来自多个数组的值显示/过滤元素
- kotlin - 如何使用 spyk