Skip to content

Visibility Events

Eli Hart edited this page Aug 13, 2019 · 7 revisions

Since 2.19.0

Epoxy supports notifying callbacks when views scroll on and off of the screen. Visibility state is defined with these terms.

  • Visible: Triggered when at least one pixel of the view is visible.
  • Invisible: Triggered when the view no longer has any pixels on the screen.
  • Focused: Event: Triggered when either the view occupies at least half of the viewport, or, if the view is smaller than half the viewport, when it is fully visible.
  • Unfocused Visible: Triggered when the view is no longer focused, i.e. it is not fully visible and does not occupy at least half the viewport.
  • Full Impression Visible: Triggered when the entire view has passed through the viewport at some point.

Enabling Visibility Events for a RecyclerView

Visibility change callbacks must be enabled on a per RecyclerView basis by using the EpoxyVisibilityTracker

val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)

This adds listeners to the RecyclerView that check for visibility changes on every scroll or layout change.

Your model and view visibility callbacks will not be called unless you do this for the RecyclerView they are shown in.

Callbacks for visibility changes

There are two interfaces that communicate visibility status.

OnModelVisibilityStateChangedListener - This notifies you when the visibility state of the view changes, such as going from invisible to visible.

OnModelVisibilityChangedListener - This is a more granular callback that tells you exactly how much of the view is visible on screen.

Registering callbacks

There are several ways to register callbacks for the two visibility interfaces.

When building a model

When you instantiate a generated model and set your data on it you can register visibility listeners for that specific model instance

new MyModel_()
   .id("my id")
   .onVisibilityStateChanged { model, view, visibilityState ->
      // Do something with the new visibility state
   } 
   .onVisibilityChanged { model, view, percentVisibleHeight, percentVisibleWidth, visibleHeight, visibleWidth ,view -> 
      // Do something with the visibility information
  }

When overriding a model

If you are creating your own EpoxyModel subclass (such as a model that uses a viewholder) then you can override the visibility callbacks directly to get notified of all events for models of that type.

class MyModel extends EpoxyModelWithHolder<Holder> {
  ... // Set up your model as usual
  
  // Override these visibility callbacks as needed

  public void onVisibilityStateChanged(@Visibility int visibilityState, @NonNull T view) {

  }

  public void onVisibilityChanged(
      @FloatRange(from = 0.0f, to = 100.0f) float percentVisibleHeight,
      @FloatRange(from = 0.0f, to = 100.0f) float percentVisibleWidth,
      @Px int visibleHeight,
      @Px int visibleWidth,
      @NonNull T view
  ) {

  }
}

Within custom views with @ModelView

If you are using the @ModelView annotation to generate models from custom views then you can annotate methods within your view to receive visibility callbacks directly in the view.

There are two annotations, one for each callback type.

OnVisibilityStateChanged

OnVisibilityChanged

You must make sure that your annotated methods have the correct signature, otherwise Epoxy will provide an error at compile time. Usage looks like:

@ModelView
class MyView extends View {

@OnVisibilityStateChange
void myMethodForWatchingVisibilityState(@VisibilityState int state) {

}

@OnVisibilityStateChanged
void myMethodForWatchingVisibility(
    float percentVisibleHeight, float percentVisibleWidth,
    int visibleHeight, int visibleWidth
){

}

Usage with EpoxyModelGroup

Note that models within an EpoxyModelGroup do not currently support registering visibility listeners on them - they will not get any visibility callbacks. Only the top level group model will receive visibility callbacks.

If you do want to register visibility callbacks on EpoxyModelGroup then you must use a generated subclass of it, so that the visibility callbacks are generated. Make a subclass of EpoxyModelGroup and add the @EpoxyModelClass annotation to it to force a generated class to be created - then use the resulting generated class.

EpoxyModelGroup could be modified in the future to have visibility handling built in - pull requests are welcome!