-
Notifications
You must be signed in to change notification settings - Fork 728
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. (#…
…560) * Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. * checkstyle failures * Fix existing tests * Send visibleSize instead of size, prevent sending duplicate events. * Remove useless private methods * Add visibility events in sample app. * Replace `@OnVisibilityEvent(event = ...)` by `@OnVisibilityChanged` and `@OnVisibilityStateChanged` Add `OnModelVisibilityStateChangedListener` and `OnModelVisibilityChangedListener` interfaces * Add the visibility listener to the model code generation * Update test resources (not sure why but the ruby script reformatted some non-related lines) * Send unfocused event on detach event * Add float ranges * Rename to sizeInScrollingDirection; * Can be private * typo * Remove dependency to LinearLayoutManager * Remove dependency to EpoxyRecyclerView. Make visibility tracker public. * parameter names inverted * Add toto regarding nested list (carousel) * visibility...() -> onVisibility...() * visibilityStateParam -> visibilityObjectParam * Add optional checkTypeParameters in validateExecutableElement, use it for visibility annotations checking. * throws if not instanceof EpoxyViewHolder * javadoc + renaming on EpoxyVisibilityItem * EpoxyModel<?> -> EpoxyModel<V> * Javadoc, links to the epoxy model methods * Remove mention to "You may clear the listener by..." in javadoc * More javadoc * Move state constants to top level class VisibilityState * Unused import * Add 3 tests : load RV, scroll to, scroll by. * Fix bugs (thanks to unit tests) * Missing annotations (FloatRange, Px) * Tests for @ModelView's @OnVisibilityChanged @OnVisibilityStateChanged codegen * Rename `is...()` to `checkAndUpdate...()` * Use constant * Add basic javadoc to EpoxyModel + todo * Prepare unit test for insert/delete case (disabled as failing) * remove dup comment
- Loading branch information
Showing
126 changed files
with
8,717 additions
and
332 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyVisibilityItem.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package com.airbnb.epoxy; | ||
|
||
import android.graphics.Rect; | ||
import android.support.annotation.NonNull; | ||
import android.support.annotation.Px; | ||
import android.support.v7.widget.RecyclerView; | ||
import android.view.View; | ||
|
||
/** | ||
* This class represent an item in the {@link android.support.v7.widget.RecyclerView} and it is | ||
* being reused with multiple model via the update method. There is 1:1 relationship between an | ||
* EpoxyVisibilityItem and a child within the {@link android.support.v7.widget.RecyclerView}. | ||
* | ||
* It contains the logic to compute the visibility state of an item. It will also invoke the | ||
* visibility callbacks on {@link com.airbnb.epoxy.EpoxyViewHolder} | ||
* | ||
* This class should remain non-public and is intended to be used by {@link EpoxyVisibilityTracker} | ||
* only. | ||
*/ | ||
class EpoxyVisibilityItem { | ||
|
||
private static final int NOT_NOTIFIED = -1; | ||
|
||
private final Rect localVisibleRect = new Rect(); | ||
|
||
private int adapterPosition = RecyclerView.NO_POSITION; | ||
|
||
@Px | ||
private int sizeInScrollingDirection; | ||
|
||
private int sizeNotInScrollingDirection; | ||
|
||
private boolean verticalScrolling; | ||
|
||
private float percentVisibleSize = 0.f; | ||
|
||
private int visibleSize; | ||
|
||
private int viewportSize; | ||
|
||
private boolean fullyVisible = false; | ||
private boolean visible = false; | ||
private boolean focusedVisible = false; | ||
|
||
/** Store last value for de-duping */ | ||
private int lastVisibleSizeNotified = NOT_NOTIFIED; | ||
|
||
/** | ||
* Update the visibility item according the current layout. | ||
* | ||
* @param view the current {@link com.airbnb.epoxy.EpoxyViewHolder}'s itemView | ||
* @param parent the {@link android.support.v7.widget.RecyclerView} | ||
* @param vertical true if it scroll vertically | ||
* @return true if the view has been measured | ||
*/ | ||
boolean update(@NonNull View view, @NonNull RecyclerView parent, | ||
boolean vertical, boolean detachEvent) { | ||
view.getLocalVisibleRect(localVisibleRect); | ||
this.verticalScrolling = vertical; | ||
if (vertical) { | ||
sizeInScrollingDirection = view.getMeasuredHeight(); | ||
sizeNotInScrollingDirection = view.getMeasuredWidth(); | ||
viewportSize = parent.getMeasuredHeight(); | ||
visibleSize = detachEvent ? 0 : localVisibleRect.height(); | ||
} else { | ||
sizeNotInScrollingDirection = view.getMeasuredHeight(); | ||
sizeInScrollingDirection = view.getMeasuredWidth(); | ||
viewportSize = parent.getMeasuredWidth(); | ||
visibleSize = detachEvent ? 0 : localVisibleRect.width(); | ||
} | ||
percentVisibleSize = detachEvent ? 0 : 100.f / sizeInScrollingDirection * visibleSize; | ||
if (visibleSize != sizeInScrollingDirection) { | ||
fullyVisible = false; | ||
} | ||
return sizeInScrollingDirection > 0; | ||
} | ||
|
||
int getAdapterPosition() { | ||
return adapterPosition; | ||
} | ||
|
||
void reset(int newAdapterPosition) { | ||
fullyVisible = false; | ||
visible = false; | ||
focusedVisible = false; | ||
adapterPosition = newAdapterPosition; | ||
lastVisibleSizeNotified = NOT_NOTIFIED; | ||
} | ||
|
||
void handleVisible(@NonNull EpoxyViewHolder epoxyHolder, boolean detachEvent) { | ||
if (visible && checkAndUpdateInvisible(detachEvent)) { | ||
epoxyHolder.visibilityStateChanged(VisibilityState.INVISIBLE); | ||
} else if (!visible && checkAndUpdateVisible()) { | ||
epoxyHolder.visibilityStateChanged(VisibilityState.VISIBLE); | ||
} | ||
} | ||
|
||
void handleFocus(EpoxyViewHolder epoxyHolder, boolean detachEvent) { | ||
if (focusedVisible && checkAndUpdateUnfocusedVisible(detachEvent)) { | ||
epoxyHolder.visibilityStateChanged(VisibilityState.UNFOCUSED_VISIBLE); | ||
} else if (!focusedVisible && checkAndUpdateFocusedVisible()) { | ||
epoxyHolder.visibilityStateChanged(VisibilityState.FOCUSED_VISIBLE); | ||
} | ||
} | ||
|
||
void handleFullImpressionVisible(EpoxyViewHolder epoxyHolder, boolean detachEvent) { | ||
if (!fullyVisible && checkAndUpdateFullImpressionVisible()) { | ||
epoxyHolder | ||
.visibilityStateChanged(VisibilityState.FULL_IMPRESSION_VISIBLE); | ||
} | ||
} | ||
|
||
void handleChanged(EpoxyViewHolder epoxyHolder) { | ||
if (visibleSize != lastVisibleSizeNotified) { | ||
if (verticalScrolling) { | ||
epoxyHolder.visibilityChanged(percentVisibleSize, 100.f, visibleSize, | ||
sizeNotInScrollingDirection); | ||
} else { | ||
epoxyHolder.visibilityChanged(100.f, percentVisibleSize, | ||
sizeNotInScrollingDirection, visibleSize); | ||
} | ||
lastVisibleSizeNotified = visibleSize; | ||
} | ||
} | ||
|
||
/** | ||
* @return true when at least one pixel of the component is visible | ||
*/ | ||
private boolean checkAndUpdateVisible() { | ||
return visible = visibleSize > 0; | ||
} | ||
|
||
/** | ||
* @param detachEvent true if initiated from detach event | ||
* @return true when when the component no longer has any pixels on the screen | ||
*/ | ||
private boolean checkAndUpdateInvisible(boolean detachEvent) { | ||
boolean invisible = visibleSize <= 0 || detachEvent; | ||
if (invisible) { | ||
visible = false; | ||
} | ||
return !visible; | ||
} | ||
|
||
/** | ||
* @return true when either the component occupies at least half of the viewport, or, if the | ||
* component is smaller than half the viewport, when it is fully visible. | ||
*/ | ||
private boolean checkAndUpdateFocusedVisible() { | ||
return focusedVisible = | ||
sizeInScrollingDirection >= viewportSize / 2 || (visibleSize == sizeInScrollingDirection | ||
&& sizeInScrollingDirection < viewportSize / 2); | ||
} | ||
|
||
/** | ||
* @param detachEvent true if initiated from detach event | ||
* @return true when the component is no longer focused, i.e. it is not fully visible and does | ||
* not occupy at least half the viewport. | ||
*/ | ||
private boolean checkAndUpdateUnfocusedVisible(boolean detachEvent) { | ||
boolean unfocusedVisible = detachEvent | ||
|| !(sizeInScrollingDirection >= viewportSize / 2 || ( | ||
visibleSize == sizeInScrollingDirection && sizeInScrollingDirection < viewportSize / 2)); | ||
if (unfocusedVisible) { | ||
focusedVisible = false; | ||
} | ||
return !focusedVisible; | ||
} | ||
|
||
/** | ||
* @return true when the entire component has passed through the viewport at some point. | ||
*/ | ||
private boolean checkAndUpdateFullImpressionVisible() { | ||
return fullyVisible = visibleSize == sizeInScrollingDirection; | ||
} | ||
} | ||
|
Oops, something went wrong.