diff --git a/epoxy-adapter/src/main/java/com/airbnb/epoxy/AggregateVisibilityState.kt b/epoxy-adapter/src/main/java/com/airbnb/epoxy/AggregateVisibilityState.kt new file mode 100644 index 0000000000..96b9ba6bb2 --- /dev/null +++ b/epoxy-adapter/src/main/java/com/airbnb/epoxy/AggregateVisibilityState.kt @@ -0,0 +1,11 @@ +package com.airbnb.epoxy + +/** + * The complete [VisibilityState]s for a single update. + */ +interface AggregateVisibilityState { + val partiallyVisible: Boolean + val fullyVisible: Boolean + val visible: Boolean + val focusedVisible: Boolean +} diff --git a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModel.java b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModel.java index b4de2a83e9..7473ee3d80 100644 --- a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModel.java +++ b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModel.java @@ -210,6 +210,16 @@ public void unbind(@NonNull T view) { public void onVisibilityStateChanged(@Visibility int visibilityState, @NonNull T view) { } + /** + * The aggregate {@link com.airbnb.epoxy.VisibilityState}s for this model that were previously + * sent to {@link #onVisibilityStateChanged}. + */ + public void onAggregateVisibilityStateChanged( + @NonNull AggregateVisibilityState visibilityState, + @NonNull T view + ) { + } + /** * TODO link to the wiki * diff --git a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyViewHolder.java b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyViewHolder.java index 188b03c8a7..576b654fc5 100644 --- a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyViewHolder.java +++ b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyViewHolder.java @@ -103,6 +103,14 @@ public void visibilityStateChanged(@Visibility int visibilityState) { epoxyModel.onVisibilityStateChanged(visibilityState, objectToBind()); } + public void aggregateVisibilityChanged( + @NonNull AggregateVisibilityState aggregateVisibilityState + ) { + assertBound(); + // noinspection unchecked + epoxyModel.onAggregateVisibilityStateChanged(aggregateVisibilityState, objectToBind()); + } + public void visibilityChanged( @FloatRange(from = 0.0f, to = 100.0f) float percentVisibleHeight, @FloatRange(from = 0.0f, to = 100.0f) float percentVisibleWidth, diff --git a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityItem.kt b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityItem.kt index ed093acfda..68ce97e62f 100644 --- a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityItem.kt +++ b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityItem.kt @@ -20,7 +20,7 @@ import androidx.recyclerview.widget.RecyclerView * only. */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -class EpoxyVisibilityItem(adapterPosition: Int? = null) { +class EpoxyVisibilityItem(adapterPosition: Int? = null) : AggregateVisibilityState { private val localVisibleRect = Rect() @@ -44,10 +44,14 @@ class EpoxyVisibilityItem(adapterPosition: Int? = null) { @Px private var viewportWidth = 0 - private var partiallyVisible = false - private var fullyVisible = false - private var visible = false - private var focusedVisible = false + override var partiallyVisible = false + private set + override var fullyVisible = false + private set + override var visible = false + private set + override var focusedVisible = false + private set private var viewVisibility = View.GONE /** Store last value for de-duping */ @@ -142,6 +146,10 @@ class EpoxyVisibilityItem(adapterPosition: Int? = null) { } } + fun handleAggregateVisibleState(epoxyHolder: EpoxyViewHolder) { + epoxyHolder.aggregateVisibilityChanged(this) + } + fun handleChanged(epoxyHolder: EpoxyViewHolder, visibilityChangedEnabled: Boolean): Boolean { var changed = false if (visibleHeight != lastVisibleHeightNotified || visibleWidth != lastVisibleWidthNotified || viewVisibility != lastVisibilityNotified) { diff --git a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityTracker.kt b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityTracker.kt index b1c4973211..6933e0e45a 100644 --- a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityTracker.kt +++ b/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityTracker.kt @@ -338,6 +338,7 @@ open class EpoxyVisibilityTracker { } vi.handleFocus(epoxyHolder, detachEvent) vi.handleFullImpressionVisible(epoxyHolder, detachEvent) + vi.handleAggregateVisibleState(epoxyHolder) changed = vi.handleChanged(epoxyHolder, onChangedEnabled) } return changed diff --git a/epoxy-adapter/src/test/java/com/airbnb/epoxy/EpoxyVisibilityTrackerTest.kt b/epoxy-adapter/src/test/java/com/airbnb/epoxy/EpoxyVisibilityTrackerTest.kt index 9ca5277267..e00716c6b7 100644 --- a/epoxy-adapter/src/test/java/com/airbnb/epoxy/EpoxyVisibilityTrackerTest.kt +++ b/epoxy-adapter/src/test/java/com/airbnb/epoxy/EpoxyVisibilityTrackerTest.kt @@ -958,6 +958,16 @@ class EpoxyVisibilityTrackerTest { helper.fullImpression = state == FULL_IMPRESSION_VISIBLE } } + + override fun onAggregateVisibilityStateChanged( + visibilityState: AggregateVisibilityState, + view: View + ) { + helper.aggregateVisibilityState.visible = visibilityState.visible + helper.aggregateVisibilityState.focusedVisible = visibilityState.focusedVisible + helper.aggregateVisibilityState.fullyVisible = visibilityState.fullyVisible + helper.aggregateVisibilityState.partiallyVisible = visibilityState.partiallyVisible + } } /** @@ -976,6 +986,8 @@ class EpoxyVisibilityTrackerTest { var partialImpression = false var fullImpression = false + val aggregateVisibilityState = MutableAggregateVisibilityState() + fun assert( id: Int? = null, visibleHeight: Int? = null, @@ -1032,6 +1044,7 @@ class EpoxyVisibilityTrackerTest { it, this.visible ) + Assert.assertEquals(it, aggregateVisibilityState.visible) } partialImpression?.let { Assert.assertEquals( @@ -1039,6 +1052,7 @@ class EpoxyVisibilityTrackerTest { it, this.partialImpression ) + Assert.assertEquals(it, aggregateVisibilityState.partiallyVisible) } fullImpression?.let { Assert.assertEquals( @@ -1046,6 +1060,7 @@ class EpoxyVisibilityTrackerTest { it, this.fullImpression ) + Assert.assertEquals(it, aggregateVisibilityState.fullyVisible) } visitedStates?.let { assertVisited(it) } } @@ -1070,6 +1085,13 @@ class EpoxyVisibilityTrackerTest { } } } + + class MutableAggregateVisibilityState : AggregateVisibilityState { + override var partiallyVisible: Boolean = false + override var fullyVisible: Boolean = false + override var visible: Boolean = false + override var focusedVisible: Boolean = false + } } }