Last active
August 4, 2016 16:08
-
-
Save caseykulm/0d51ef4d4979be176e89bcc84220c10d to your computer and use it in GitHub Desktop.
New and improved helper class to determine when view enter/exits the visible viewport
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
public class EventView { | |
public interface EventViewListener<ViewType extends View> { | |
/** | |
* @param onScreen When the view enters/exits the viewport. Not to be confused with {@link View#VISIBLE}. | |
*/ | |
void onVisibleOnScreenStateChanged(boolean onScreen); | |
/** | |
* Verbose method that is called every time the view hierarchy has the potential to | |
* move this view in/out of the viewport. Useful for debugging, but will be called | |
* often so make sure not to call anything expensive here. | |
* | |
* @param viewType The view that all view state events are listening to | |
* @param onScreen Whether the view is currently in the viewport or not | |
*/ | |
void onProcessEvent(ViewType viewType, boolean onScreen); | |
/** | |
* Called whenever a view enters the viewport. Will also be called on the same view, | |
* if the id associated with it is different than the last time the view entered the | |
* viewport. | |
*/ | |
void onFirstTimeOnScreen(); | |
} | |
public static <ViewType extends View> void bind(ViewType view, EventViewListener<ViewType> listener, String id) { | |
EventView.ViewsMovingListener<ViewType> viewsMovingListener = new EventView.ViewsMovingListener<ViewType>(view, listener, id); | |
view.getViewTreeObserver().addOnGlobalLayoutListener(viewsMovingListener); | |
view.getViewTreeObserver().addOnScrollChangedListener(viewsMovingListener); | |
} | |
static class Subject<ViewType extends View> { | |
private ViewType bvView; | |
private EventViewListener<ViewType> eventViewListener; | |
private final Rect rect; | |
private final Point globalOffset; | |
private boolean lastOnScreenState = false; | |
private final String id; | |
Subject(ViewType view, EventViewListener<ViewType> eventViewListener, String id) { | |
this.bvView = view; | |
this.eventViewListener = eventViewListener; | |
this.rect = new Rect(); | |
this.globalOffset = new Point(); | |
this.id = id; | |
} | |
void processEvent() { | |
boolean onScreen = eventViewVisible(); | |
eventViewListener.onProcessEvent(bvView, onScreen); | |
if (onScreen != lastOnScreenState) { | |
eventViewListener.onVisibleOnScreenStateChanged(onScreen); | |
} | |
if (onScreen && !seenOnScreenWithId(bvView, id)) { | |
eventViewListener.onFirstTimeOnScreen(); | |
} | |
lastOnScreenState = onScreen; | |
} | |
private boolean eventViewVisible() { | |
if (bvView == null) { | |
return false; | |
} | |
boolean isVisibleOnScreen = bvView.getGlobalVisibleRect(rect, globalOffset); | |
return isVisibleOnScreen; | |
} | |
} | |
static class ViewUtil { | |
/** | |
* Only call this when the View was seen in the viewport with an associated id | |
* | |
* @param view The View that will be tracked | |
* @param id A unique id to associate with this view | |
* @param <ViewType> The type of View child class | |
* @return Whether this View+Id has already been seen in the viewport | |
*/ | |
static <ViewType extends View> boolean seenOnScreenWithId(ViewType view, String id) { | |
Object seenIdsForViewObj = view.getTag(R.string.seen_product_onscreen_with_id_set); | |
if (seenIdsForViewObj == null) { | |
Set<String> seenIdsForView = new HashSet<String>(); | |
updateSeenId(seenIdsForView, view, id); | |
return false; | |
} else if (seenIdsForViewObj instanceof Set) { | |
Set<String> seenIdsForView = (Set<String>) seenIdsForViewObj; | |
if (seenIdsForView.contains(id)) { | |
return true; | |
} else { | |
updateSeenId(seenIdsForView, view, id); | |
return false; | |
} | |
} else { | |
throw new IllegalStateException("ids associated with view are not in expected state"); | |
} | |
} | |
static <ViewType extends View> void updateSeenId(Set<String> seenIdsForView, ViewType view, String id) { | |
Log.d(EventView.class.getSimpleName(), "onProcessEvent() - First time seeing " + ((TextView) view).getText() + " on screen!"); | |
seenIdsForView.add(id); | |
view.setTag(R.string.seen_product_onscreen_with_id_set, seenIdsForView); | |
} | |
} | |
static class ViewsMovingListener<ViewType extends View> implements ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener { | |
private final Subject<ViewType> eventViewSubject; | |
ViewsMovingListener(ViewType view, EventViewListener<ViewType> eventViewListener, String id) { | |
this.eventViewSubject = new Subject<ViewType>(view, eventViewListener, id); | |
} | |
@Override | |
public void onScrollChanged() { | |
eventViewSubject.processEvent(); | |
} | |
@Override | |
public void onGlobalLayout() { | |
eventViewSubject.processEvent(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment