Skip to content

Commit

Permalink
Augment SlideInItemAnimator to use springs for move animations.
Browse files Browse the repository at this point in the history
  • Loading branch information
nickbutcher committed Jul 11, 2019
1 parent 5088419 commit 3db0f59
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/*
* Copyright 2018 Google LLC
* Copyright 2019 Google, Inc.
*
* 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
* 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.
* 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 io.plaidapp.core.ui.recyclerview
Expand All @@ -37,12 +36,11 @@ open class SlideInItemAnimator @JvmOverloads constructor(
layoutDirection: Int = -1
) : DefaultItemAnimator() {

private val pendingAdds = mutableListOf<RecyclerView.ViewHolder>()
private val slideFromEdge: Int = Gravity.getAbsoluteGravity(slideFromEdge, layoutDirection)

init {
addDuration = 160L
}
private val pendingAdds = mutableListOf<RecyclerView.ViewHolder>()
private val runningAdds = mutableListOf<RecyclerView.ViewHolder>()
private val pendingMoves = mutableListOf<RecyclerView.ViewHolder>()
private val runningMoves = mutableListOf<RecyclerView.ViewHolder>()

@SuppressLint("RtlHardcoded")
override fun animateAdd(holder: RecyclerView.ViewHolder): Boolean {
Expand All @@ -58,55 +56,131 @@ open class SlideInItemAnimator @JvmOverloads constructor(
return true
}

override fun animateMove(
holder: RecyclerView.ViewHolder?,
fromViewX: Int,
fromViewY: Int,
toViewX: Int,
toViewY: Int
): Boolean {
holder ?: return false
val view = holder.itemView
val fromX = fromViewX + holder.itemView.translationX.toInt()
val fromY = fromViewY + holder.itemView.translationY.toInt()
endAnimation(holder)
val deltaX = toViewX - fromX
val deltaY = toViewY - fromY
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder)
return false
}
if (deltaX != 0) {
view.translationX = (-deltaX).toFloat()
}
if (deltaY != 0) {
view.translationY = (-deltaY).toFloat()
}
pendingMoves.add(holder)
return true
}

override fun runPendingAnimations() {
super.runPendingAnimations()
if (pendingAdds.isNotEmpty()) {
for (i in pendingAdds.indices.reversed()) {
val holder = pendingAdds[i]
val springAlpha = holder.itemView.spring(ALPHA)
val springTranslationX = holder.itemView.spring(TRANSLATION_X)
val springTranslationY = holder.itemView.spring(TRANSLATION_Y)
dispatchAddStarting(holder)
springAlpha.animateToFinalPosition(1f)
springTranslationX.animateToFinalPosition(0f)
springTranslationY.animateToFinalPosition(0f)

listenForAllSpringsEnd({ cancelled ->
if (cancelled) {
clearAnimatedValues(holder.itemView)
}
dispatchAddFinished(holder)
dispatchFinishedWhenDone()

}, springAlpha, springTranslationX, springTranslationY)
pendingAdds.removeAt(i)
addItem(pendingAdds.removeAt(i))
}
}

if (pendingMoves.isNotEmpty()) {
for (i in pendingMoves.indices.reversed()) {
moveItem(pendingMoves.removeAt(i))
}
}
}

override fun endAnimation(holder: RecyclerView.ViewHolder) {
holder.itemView.spring(ALPHA).cancel()
holder.itemView.spring(TRANSLATION_X).cancel()
holder.itemView.spring(TRANSLATION_Y).cancel()
if (pendingAdds.remove(holder)) {
dispatchAddFinished(holder)
clearAnimatedValues(holder.itemView)
}
if (pendingAdds.contains(holder)) endPendingAdd(holder)
if (runningAdds.contains(holder)) endRunningAdd(holder)
if (pendingMoves.contains(holder)) endPendingMove(holder)
if (runningMoves.contains(holder)) endRunningMove(holder)
super.endAnimation(holder)
}

override fun endAnimations() {
for (i in pendingAdds.indices.reversed()) {
val holder = pendingAdds[i]
clearAnimatedValues(holder.itemView)
dispatchAddFinished(holder)
pendingAdds.removeAt(i)
}
pendingAdds.forEach(::endPendingAdd)
runningAdds.forEach(::endRunningAdd)
pendingMoves.forEach(::endPendingMove)
runningMoves.forEach(::endRunningMove)
super.endAnimations()
}

override fun isRunning(): Boolean {
return pendingAdds.isNotEmpty() || super.isRunning()
override fun isRunning() =
pendingAdds.isNotEmpty() ||
runningAdds.isNotEmpty() ||
pendingMoves.isNotEmpty() ||
runningMoves.isNotEmpty() ||
super.isRunning()

private fun addItem(holder: RecyclerView.ViewHolder) {
val springAlpha = holder.itemView.spring(ALPHA)
val springTranslationX = holder.itemView.spring(TRANSLATION_X)
val springTranslationY = holder.itemView.spring(TRANSLATION_Y)
listenForAllSpringsEnd({ cancelled ->
if (cancelled) {
clearAnimatedValues(holder.itemView)
}
dispatchAddFinished(holder)
dispatchFinishedWhenDone()
runningAdds -= holder
}, springAlpha, springTranslationX, springTranslationY)
springAlpha.animateToFinalPosition(1f)
springTranslationX.animateToFinalPosition(0f)
springTranslationY.animateToFinalPosition(0f)
dispatchAddStarting(holder)
runningAdds += holder
}

private fun moveItem(holder: RecyclerView.ViewHolder) {
val springX = holder.itemView.spring(TRANSLATION_X)
val springY = holder.itemView.spring(TRANSLATION_Y)
listenForAllSpringsEnd({ cancelled ->
if (cancelled) {
clearAnimatedValues(holder.itemView)
}
dispatchMoveFinished(holder)
dispatchFinishedWhenDone()
runningMoves -= holder
}, springX, springY)
springX.animateToFinalPosition(0f)
springY.animateToFinalPosition(0f)
dispatchMoveStarting(holder)
runningMoves += holder
}

private fun endPendingAdd(holder: RecyclerView.ViewHolder) {
clearAnimatedValues(holder.itemView)
dispatchAddFinished(holder)
pendingAdds -= holder
}

private fun endRunningAdd(holder: RecyclerView.ViewHolder) {
holder.itemView.spring(ALPHA).cancel()
holder.itemView.spring(TRANSLATION_X).cancel()
holder.itemView.spring(TRANSLATION_Y).cancel()
runningAdds -= holder
}

private fun endPendingMove(holder: RecyclerView.ViewHolder) {
clearAnimatedValues(holder.itemView)
dispatchMoveFinished(holder)
pendingMoves -= holder
}

private fun endRunningMove(holder: RecyclerView.ViewHolder) {
holder.itemView.spring(TRANSLATION_X).cancel()
holder.itemView.spring(TRANSLATION_Y).cancel()
runningMoves -= holder
}

private fun dispatchFinishedWhenDone() {
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/io/plaidapp/core/util/SpringUtils.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright 2019 Google, Inc.
*
* 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 io.plaidapp.core.util

import android.view.View
Expand Down

0 comments on commit 3db0f59

Please sign in to comment.