in litho-rendercore-visibility/src/main/java/com/facebook/rendercore/visibility/VisibilityMountExtension.java [203:397]
private static void processVisibilityOutputsNonInc(
final ExtensionState<VisibilityMountExtensionState> extensionState,
@Nullable Rect localVisibleRect,
boolean isDirty) {
final Rect previousVisibleRect = extensionState.getState().mPreviousLocalVisibleRect;
if (localVisibleRect == null || (!isDirty && previousVisibleRect.equals(localVisibleRect))) {
if (VisibilityExtensionConfigs.isDebugLoggingEnabled) {
Log.d(
DEBUG_TAG,
"Skip Processing: "
+ "[isDirty="
+ isDirty
+ ", previousVisibleRect="
+ previousVisibleRect
+ "]");
}
return;
}
final VisibilityMountExtensionState state = extensionState.getState();
final int size = state.mVisibilityOutputs.size();
if (VisibilityExtensionConfigs.isDebugLoggingEnabled) {
Log.d(DEBUG_TAG, "Visibility Outputs to process: " + size);
}
final boolean isTracing = RenderCoreSystrace.isEnabled();
final Rect intersection = new Rect();
for (int j = 0; j < size; j++) {
final VisibilityOutput visibilityOutput = state.mVisibilityOutputs.get(j);
final String componentName = visibilityOutput.getKey();
if (VisibilityExtensionConfigs.isDebugLoggingEnabled) {
Log.d(DEBUG_TAG, "Processing Visibility for: " + componentName);
}
if (isTracing) {
RenderCoreSystrace.beginSection("visibilityHandlers:" + componentName);
}
final Rect visibilityOutputBounds = visibilityOutput.getBounds();
final boolean boundsIntersect =
intersection.setIntersect(visibilityOutputBounds, localVisibleRect);
final boolean isFullyVisible = boundsIntersect && intersection.equals(visibilityOutputBounds);
final String visibilityOutputId = visibilityOutput.getId();
VisibilityItem visibilityItem = state.mVisibilityIdToItemMap.get(visibilityOutputId);
final boolean wasFullyVisible;
if (visibilityItem != null) {
wasFullyVisible = visibilityItem.wasFullyVisible();
visibilityItem.setWasFullyVisible(isFullyVisible);
} else {
wasFullyVisible = false;
}
if (isFullyVisible
&& wasFullyVisible
&& VisibilityExtensionConfigs.skipVisChecksForFullyVisible) {
// VisibilityOutput is still fully visible, no new events to dispatch, skip to next
if (isTracing) {
RenderCoreSystrace.endSection();
}
visibilityItem.setDoNotClearInThisPass(isDirty);
continue;
}
final Function<Void> visibleHandler = visibilityOutput.getVisibleEventHandler();
final Function<Void> focusedHandler = visibilityOutput.getFocusedEventHandler();
final Function<Void> unfocusedHandler = visibilityOutput.getUnfocusedEventHandler();
final Function<Void> fullImpressionHandler = visibilityOutput.getFullImpressionEventHandler();
final Function<Void> invisibleHandler = visibilityOutput.getInvisibleEventHandler();
final Function<Void> visibilityChangedHandler =
visibilityOutput.getVisibilityChangedEventHandler();
final boolean isCurrentlyVisible =
boundsIntersect
&& isInVisibleRange(visibilityOutput, visibilityOutputBounds, intersection);
if (visibilityItem != null) {
// If we did a relayout due to e.g. a state update then the handlers will have changed,
// so we should keep them up to date.
visibilityItem.setUnfocusedHandler(unfocusedHandler);
visibilityItem.setInvisibleHandler(invisibleHandler);
if (!isCurrentlyVisible) {
// Either the component is invisible now, but used to be visible, or the key on the
// component has changed so we should generate new visibility events for the new
// component.
if (visibilityItem.getInvisibleHandler() != null) {
VisibilityUtils.dispatchOnInvisible(visibilityItem.getInvisibleHandler());
}
if (visibilityChangedHandler != null) {
VisibilityUtils.dispatchOnVisibilityChanged(
visibilityChangedHandler, 0, 0, 0, 0, 0f, 0f);
}
if (visibilityItem.isInFocusedRange()) {
visibilityItem.setFocusedRange(false);
if (visibilityItem.getUnfocusedHandler() != null) {
VisibilityUtils.dispatchOnUnfocused(visibilityItem.getUnfocusedHandler());
}
}
state.mVisibilityIdToItemMap.remove(visibilityOutputId);
visibilityItem = null;
} else {
// Processed, do not clear.
visibilityItem.setDoNotClearInThisPass(isDirty);
}
}
if (isCurrentlyVisible) {
// The component is visible now, but used to be outside the viewport.
if (visibilityItem == null) {
final String globalKey = visibilityOutput.getId();
visibilityItem =
new VisibilityItem(
globalKey, invisibleHandler, unfocusedHandler, visibilityChangedHandler);
visibilityItem.setDoNotClearInThisPass(isDirty);
visibilityItem.setWasFullyVisible(isFullyVisible);
state.mVisibilityIdToItemMap.put(visibilityOutputId, visibilityItem);
if (visibleHandler != null) {
final Object content =
visibilityOutput.hasMountableContent
? getContentById(extensionState, visibilityOutput.mRenderUnitId)
: null;
VisibilityUtils.dispatchOnVisible(visibleHandler, content);
}
}
// Check if the component has entered or exited the focused range.
if (focusedHandler != null || unfocusedHandler != null) {
if (isInFocusedRange(extensionState, visibilityOutputBounds, intersection)) {
if (!visibilityItem.isInFocusedRange()) {
visibilityItem.setFocusedRange(true);
if (focusedHandler != null) {
VisibilityUtils.dispatchOnFocused(focusedHandler);
}
}
} else {
if (visibilityItem.isInFocusedRange()) {
visibilityItem.setFocusedRange(false);
if (unfocusedHandler != null) {
VisibilityUtils.dispatchOnUnfocused(unfocusedHandler);
}
}
}
}
// If the component has not entered the full impression range yet, make sure to update the
// information about the visible edges.
if (fullImpressionHandler != null && !visibilityItem.isInFullImpressionRange()) {
visibilityItem.setVisibleEdges(visibilityOutputBounds, intersection);
if (visibilityItem.isInFullImpressionRange()) {
VisibilityUtils.dispatchOnFullImpression(fullImpressionHandler);
}
}
if (visibilityChangedHandler != null) {
final int visibleWidth = getVisibleWidth(intersection);
final int visibleHeight = getVisibleHeight(intersection);
VisibilityUtils.dispatchOnVisibilityChanged(
visibilityChangedHandler,
getVisibleTop(visibilityOutputBounds, intersection),
getVisibleLeft(visibilityOutputBounds, intersection),
visibleWidth,
visibleHeight,
100f * visibleWidth / visibilityOutputBounds.width(),
100f * visibleHeight / visibilityOutputBounds.height());
}
}
if (isTracing) {
RenderCoreSystrace.endSection();
}
}
final MountDelegate mountDelegate = extensionState.getMountDelegate();
for (long id : state.mRenderUnitIdsWhichHostRenderTrees) {
if (VisibilityExtensionConfigs.isDebugLoggingEnabled) {
Log.d(DEBUG_TAG, "RecursivelyNotify:RenderUnit[id=" + id + "]");
}
mountDelegate.notifyVisibleBoundsChangedForItem(mountDelegate.getContentById(id));
}
if (isDirty) {
clearVisibilityItems(extensionState);
}
}