blob: 727204ff8896cd0af74d94270ad341d8c25615fa [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.view.View
import androidx.collection.ArrayMap
import androidx.recyclerview.widget.RecyclerView.State
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.recyclerview.widget.RecyclerView.ItemAnimator
import androidx.recyclerview.widget.SimpleItemAnimator
class ItemAnimator : SimpleItemAnimator() {
private val mAddAnimatorsList: MutableList<Animator> = ArrayList()
private val mRemoveAnimatorsList: MutableList<Animator> = ArrayList()
private val mChangeAnimatorsList: MutableList<Animator> = ArrayList()
private val mMoveAnimatorsList: MutableList<Animator> = ArrayList()
private val mAnimators: MutableMap<ViewHolder, Animator> = ArrayMap()
override fun animateRemove(holder: ViewHolder): Boolean {
endAnimation(holder)
val prevAlpha: Float = holder.itemView.getAlpha()
val removeAnimator: Animator? = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 0f)
removeAnimator!!.duration = getRemoveDuration()
removeAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchRemoveStarting(holder)
}
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
mAnimators.remove(holder)
holder.itemView.setAlpha(prevAlpha)
dispatchRemoveFinished(holder)
}
})
mRemoveAnimatorsList.add(removeAnimator)
mAnimators[holder] = removeAnimator
return true
}
override fun animateAdd(holder: ViewHolder): Boolean {
endAnimation(holder)
val prevAlpha: Float = holder.itemView.getAlpha()
holder.itemView.setAlpha(0f)
val addAnimator: Animator = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 1f)
.setDuration(getAddDuration())
addAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchAddStarting(holder)
}
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
mAnimators.remove(holder)
holder.itemView.setAlpha(prevAlpha)
dispatchAddFinished(holder)
}
})
mAddAnimatorsList.add(addAnimator)
mAnimators[holder] = addAnimator
return true
}
override fun animateMove(
holder: ViewHolder,
fromX: Int,
fromY: Int,
toX: Int,
toY: Int
): Boolean {
endAnimation(holder)
val deltaX = toX - fromX
val deltaY = toY - fromY
val moveDuration: Long = getMoveDuration()
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder)
return false
}
val view: View = holder.itemView
val prevTranslationX = view.translationX
val prevTranslationY = view.translationY
view.translationX = -deltaX.toFloat()
view.translationY = -deltaY.toFloat()
val moveAnimator: ObjectAnimator?
if (deltaX != 0 && deltaY != 0) {
val moveX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0f)
val moveY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f)
moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX, moveY)
} else if (deltaX != 0) {
val moveX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0f)
moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX)
} else {
val moveY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f)
moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveY)
}
moveAnimator?.duration = moveDuration
moveAnimator.interpolator = AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN
moveAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchMoveStarting(holder)
}
override fun onAnimationEnd(animator: Animator) {
animator?.removeAllListeners()
mAnimators.remove(holder)
view.translationX = prevTranslationX
view.translationY = prevTranslationY
dispatchMoveFinished(holder)
}
})
mMoveAnimatorsList.add(moveAnimator)
mAnimators[holder] = moveAnimator
return true
}
override fun animateChange(
oldHolder: ViewHolder,
newHolder: ViewHolder,
preInfo: ItemHolderInfo,
postInfo: ItemHolderInfo
): Boolean {
endAnimation(oldHolder)
endAnimation(newHolder)
val changeDuration: Long = getChangeDuration()
val payloads = if (preInfo is PayloadItemHolderInfo) preInfo.payloads else null
if (oldHolder === newHolder) {
val animator = (newHolder as OnAnimateChangeListener)
.onAnimateChange(payloads, preInfo.left, preInfo.top, preInfo.right,
preInfo.bottom, changeDuration)
if (animator == null) {
dispatchChangeFinished(newHolder, false)
return false
}
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchChangeStarting(newHolder, false)
}
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
mAnimators.remove(newHolder)
dispatchChangeFinished(newHolder, false)
}
})
mChangeAnimatorsList.add(animator)
mAnimators[newHolder] = animator
return true
} else if (oldHolder !is OnAnimateChangeListener ||
newHolder !is OnAnimateChangeListener) {
// Both holders must implement OnAnimateChangeListener in order to animate.
dispatchChangeFinished(oldHolder, true)
dispatchChangeFinished(newHolder, true)
return false
}
val oldChangeAnimator = (oldHolder as OnAnimateChangeListener)
.onAnimateChange(oldHolder, newHolder, changeDuration)
if (oldChangeAnimator != null) {
oldChangeAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchChangeStarting(oldHolder, true)
}
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
mAnimators.remove(oldHolder)
dispatchChangeFinished(oldHolder, true)
}
})
mAnimators[oldHolder] = oldChangeAnimator
mChangeAnimatorsList.add(oldChangeAnimator)
} else {
dispatchChangeFinished(oldHolder, true)
}
val newChangeAnimator = (newHolder as OnAnimateChangeListener)
.onAnimateChange(oldHolder, newHolder, changeDuration)
if (newChangeAnimator != null) {
newChangeAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
dispatchChangeStarting(newHolder, false)
}
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
mAnimators.remove(newHolder)
dispatchChangeFinished(newHolder, false)
}
})
mAnimators[newHolder] = newChangeAnimator
mChangeAnimatorsList.add(newChangeAnimator)
} else {
dispatchChangeFinished(newHolder, false)
}
return true
}
override fun animateChange(
oldHolder: ViewHolder,
newHolder: ViewHolder,
fromLeft: Int,
fromTop: Int,
toLeft: Int,
toTop: Int
): Boolean {
/* Unused */
throw IllegalStateException("This method should not be used")
}
override fun runPendingAnimations() {
val removeAnimatorSet = AnimatorSet()
removeAnimatorSet.playTogether(mRemoveAnimatorsList)
mRemoveAnimatorsList.clear()
val addAnimatorSet = AnimatorSet()
addAnimatorSet.playTogether(mAddAnimatorsList)
mAddAnimatorsList.clear()
val changeAnimatorSet = AnimatorSet()
changeAnimatorSet.playTogether(mChangeAnimatorsList)
mChangeAnimatorsList.clear()
val moveAnimatorSet = AnimatorSet()
moveAnimatorSet.playTogether(mMoveAnimatorsList)
mMoveAnimatorsList.clear()
val pendingAnimatorSet = AnimatorSet()
pendingAnimatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator) {
animator.removeAllListeners()
dispatchFinishedWhenDone()
}
})
// Required order: removes, then changes & moves simultaneously, then additions. There are
// redundant edges because changes or moves may be empty, causing the removes to incorrectly
// play immediately.
pendingAnimatorSet.play(removeAnimatorSet).before(changeAnimatorSet)
pendingAnimatorSet.play(removeAnimatorSet).before(moveAnimatorSet)
pendingAnimatorSet.play(changeAnimatorSet).with(moveAnimatorSet)
pendingAnimatorSet.play(addAnimatorSet).after(changeAnimatorSet)
pendingAnimatorSet.play(addAnimatorSet).after(moveAnimatorSet)
pendingAnimatorSet.start()
}
override fun endAnimation(holder: ViewHolder) {
val animator = mAnimators[holder]
mAnimators.remove(holder)
mAddAnimatorsList.remove(animator)
mRemoveAnimatorsList.remove(animator)
mChangeAnimatorsList.remove(animator)
mMoveAnimatorsList.remove(animator)
animator?.end()
dispatchFinishedWhenDone()
}
override fun endAnimations() {
val animatorList: MutableList<Animator?> = ArrayList(mAnimators.values)
for (animator in animatorList) {
animator?.end()
}
dispatchFinishedWhenDone()
}
override fun isRunning(): Boolean = mAnimators.isNotEmpty()
private fun dispatchFinishedWhenDone() {
if (!isRunning()) {
dispatchAnimationsFinished()
}
}
override fun recordPreLayoutInformation(
state: State,
viewHolder: ViewHolder,
@AdapterChanges changeFlags: Int,
payloads: MutableList<Any>
): ItemAnimator.ItemHolderInfo {
val itemHolderInfo: ItemHolderInfo =
super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads)
if (itemHolderInfo is PayloadItemHolderInfo) {
itemHolderInfo.payloads = payloads
}
return itemHolderInfo
}
override fun obtainHolderInfo(): ItemAnimator.ItemHolderInfo {
return PayloadItemHolderInfo()
}
override fun canReuseUpdatedViewHolder(
viewHolder: ViewHolder,
payloads: MutableList<Any?>
): Boolean {
val defaultReusePolicy: Boolean = super.canReuseUpdatedViewHolder(viewHolder, payloads)
// Whenever we have a payload, this is an in-place animation.
return payloads.isNotEmpty() || defaultReusePolicy
}
private class PayloadItemHolderInfo : ItemHolderInfo() {
private val mPayloads: MutableList<Any> = ArrayList()
var payloads: MutableList<Any>
get() = mPayloads
set(payloads) {
mPayloads.clear()
mPayloads.addAll(payloads)
}
}
interface OnAnimateChangeListener {
fun onAnimateChange(oldHolder: ViewHolder, newHolder: ViewHolder, duration: Long): Animator?
fun onAnimateChange(
payloads: List<Any>?,
fromLeft: Int,
fromTop: Int,
fromRight: Int,
fromBottom: Int,
duration: Long
): Animator?
}
}