/* * 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 = ArrayList() private val mRemoveAnimatorsList: MutableList = ArrayList() private val mChangeAnimatorsList: MutableList = ArrayList() private val mMoveAnimatorsList: MutableList = ArrayList() private val mAnimators: MutableMap = 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 = 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 ): 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 ): 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 = ArrayList() var payloads: MutableList get() = mPayloads set(payloads) { mPayloads.clear() mPayloads.addAll(payloads) } } interface OnAnimateChangeListener { fun onAnimateChange(oldHolder: ViewHolder, newHolder: ViewHolder, duration: Long): Animator? fun onAnimateChange( payloads: List?, fromLeft: Int, fromTop: Int, fromRight: Int, fromBottom: Int, duration: Long ): Animator? } }