Skip to content

Commit 354e5c1

Browse files
author
Saket Narayan
committed
Fix: item change animations get canceled by ItemExpandAnimator
It's surprising that this bug flew under the radar for so long. ItemExpandAnimator registers a global layout listener on ExpandablePageLayout that was originally written to react to size changes and synchronize RecyclerView items with the page. As a side effect, it also used to override item add/remove/change/move animations. This commit modifies the implementation to ignore global layout callbacks unless the page's location or size changes.
1 parent f3ca4b0 commit 354e5c1

2 files changed

Lines changed: 49 additions & 30 deletions

File tree

inboxrecyclerview/src/main/java/me/saket/inboxrecyclerview/animation/ItemExpandAnimator.kt

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,27 @@
11
package me.saket.inboxrecyclerview.animation
22

3-
import android.graphics.Rect
4-
import android.view.ViewTreeObserver
53
import me.saket.inboxrecyclerview.InboxRecyclerView
6-
import me.saket.inboxrecyclerview.page.ExpandablePageLayout
74

85
/**
96
* Controls how [InboxRecyclerView] items are animated when its page is moving.
107
* To create a custom animator, extend this and override [onPageMove].
118
*/
129
abstract class ItemExpandAnimator {
1310

14-
private val pagePreDrawListener = object : ViewTreeObserver.OnPreDrawListener {
15-
private var lastTranslationY = 0F
16-
private var lastClippedDimens = Rect()
17-
private var lastState = ExpandablePageLayout.PageState.COLLAPSED
18-
19-
override fun onPreDraw(): Boolean {
20-
val page = recyclerView.page
21-
if (lastTranslationY != page.translationY || lastClippedDimens != page.clippedDimens || lastState != page.currentState) {
22-
onPageMove()
23-
}
24-
25-
lastTranslationY = page.translationY
26-
lastClippedDimens = page.clippedDimens
27-
lastState = page.currentState
28-
return true
29-
}
30-
}
31-
32-
private val pageLayoutChangeListener = {
33-
// Changes in the page's dimensions will get handled here.
34-
onPageMove()
35-
}
36-
3711
protected lateinit var recyclerView: InboxRecyclerView
12+
private lateinit var changeDetector: PageLocationChangeDetector
3813

3914
fun onAttachRecyclerView(recyclerView: InboxRecyclerView) {
4015
this.recyclerView = recyclerView
41-
recyclerView.page.viewTreeObserver.addOnGlobalLayoutListener(pageLayoutChangeListener)
42-
recyclerView.page.viewTreeObserver.addOnPreDrawListener(pagePreDrawListener)
16+
this.changeDetector = PageLocationChangeDetector(recyclerView.page, changeListener = ::onPageMove)
17+
18+
recyclerView.page.viewTreeObserver.addOnGlobalLayoutListener(changeDetector)
19+
recyclerView.page.viewTreeObserver.addOnPreDrawListener(changeDetector)
4320
}
4421

4522
fun onDetachRecyclerView(recyclerView: InboxRecyclerView) {
46-
recyclerView.page.viewTreeObserver.removeOnGlobalLayoutListener(pageLayoutChangeListener)
47-
recyclerView.page.viewTreeObserver.removeOnPreDrawListener(pagePreDrawListener)
23+
recyclerView.page.viewTreeObserver.removeOnGlobalLayoutListener(changeDetector)
24+
recyclerView.page.viewTreeObserver.removeOnPreDrawListener(changeDetector)
4825
}
4926

5027
/**
@@ -57,6 +34,9 @@ abstract class ItemExpandAnimator {
5734

5835
companion object {
5936

37+
/**
38+
* See [SplitExpandAnimator].
39+
*/
6040
@JvmStatic
6141
fun split() = SplitExpandAnimator()
6242
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package me.saket.inboxrecyclerview.animation
2+
3+
import android.graphics.Rect
4+
import android.view.ViewTreeObserver
5+
import me.saket.inboxrecyclerview.page.ExpandablePageLayout
6+
7+
internal class PageLocationChangeDetector(
8+
private val page: ExpandablePageLayout,
9+
private val changeListener: () -> Unit
10+
) : ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnGlobalLayoutListener {
11+
12+
private var lastTranslationY = 0F
13+
private var lastClippedDimens = Rect()
14+
private var lastState = ExpandablePageLayout.PageState.COLLAPSED
15+
16+
override fun onPreDraw(): Boolean {
17+
dispatchCallbackIfNeeded()
18+
return true
19+
}
20+
21+
override fun onGlobalLayout() {
22+
// Changes in the page's dimensions will get handled here.
23+
dispatchCallbackIfNeeded()
24+
}
25+
26+
private fun dispatchCallbackIfNeeded() {
27+
val moved = lastTranslationY != page.translationY
28+
val dimensionsChanged = lastClippedDimens != page.clippedDimens
29+
val stateChanged = lastState != page.currentState
30+
31+
if (page.isCollapsed.not() && (moved || dimensionsChanged || stateChanged)) {
32+
changeListener()
33+
}
34+
35+
lastTranslationY = page.translationY
36+
lastClippedDimens = page.clippedDimens
37+
lastState = page.currentState
38+
}
39+
}

0 commit comments

Comments
 (0)