@@ -698,17 +698,78 @@ void performIncrementalMountForVisibleBoundsChange() {
698698 // Per ComponentTree visible area. Because BaseMountingViews can be nested and mounted
699699 // not in "depth order", this variable cannot be static.
700700 final Rect currentVisibleArea = new Rect ();
701- final boolean hasNonEmptyVisibleRect = getLocalVisibleRect (currentVisibleArea );
701+ final boolean areBoundsVisible = getLocalVisibleRect (currentVisibleArea );
702702
703- if (hasNonEmptyVisibleRect
703+ if (areBoundsVisible
704704 || hasComponentsExcludedFromIncrementalMount (getCurrentLayoutState ())
705705 // It might not be yet visible but animating from 0 height/width in which case we still
706706 // need to mount them to trigger animation.
707- || animatingRootBoundsFromZero (currentVisibleArea )) {
707+ || animatingRootBoundsFromZero (currentVisibleArea )
708+ || hasBecomeInvisible ()) {
708709 mountComponent (currentVisibleArea , true );
709710 }
710711 }
711712
713+ /**
714+ * This is used to detect an edge case of using Litho in a nested scenario. You can imagine a a
715+ * host XML, which takes a LithoView on top.
716+ *
717+ * <p>As we scroll the LithoView out of the screen, we will process incremental mount correctly
718+ * until the last visible pixel.
719+ *
720+ * <pre>
721+ * |________________________| top: 0
722+ * || ||
723+ * || Litho View ||
724+ * ||______________________|| bottom: 156
725+ * | |
726+ * | |
727+ * | Remaining Host |
728+ * | |
729+ * |_______________________ |
730+ * </pre>
731+ *
732+ * However, once the LithoView goes off the screen but the remaining host is still visible, the
733+ * LithoView rect coordinates become negative:
734+ *
735+ * <pre>
736+ * |________________________| top: -156
737+ * || ||
738+ * || Litho View ||
739+ * ||______________________|| bottom: 0
740+ *
741+ * invisible
742+ * - - - - - - - - - - - - - - - - - - - - - -
743+ * visible
744+ * |_______________________ |
745+ * | |
746+ * | Remaining Host |
747+ * | |
748+ * |_______________________ |
749+ * </pre>
750+ *
751+ * Therefore, the rect is considered not visible, and in normal conditions we wouldn't process an
752+ * extra pass of incremental mount.
753+ *
754+ * <p>We use this check to understand if in the last IM pass, the rect was visible, and that now
755+ * is not. If that is the case, we will do an extra pass of IM to guarantee that the visibility
756+ * outputs are processed and any onInvisible callback is delivered.
757+ */
758+ private boolean hasBecomeInvisible () {
759+ ComponentsConfiguration configuration = getConfiguration ();
760+ boolean shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible =
761+ configuration != null
762+ && configuration .shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible ;
763+
764+ return shouldNotifyVisibleBoundsChangeWhenNestedLithoViewBecomesInvisible
765+ && mPreviousMountVisibleRectBounds .bottom >= 0
766+ && mPreviousMountVisibleRectBounds .top >= 0
767+ && mPreviousMountVisibleRectBounds .height () > 0
768+ && mPreviousMountVisibleRectBounds .left >= 0
769+ && mPreviousMountVisibleRectBounds .right >= 0
770+ && mPreviousMountVisibleRectBounds .width () > 0 ;
771+ }
772+
712773 private boolean animatingRootBoundsFromZero (Rect currentVisibleArea ) {
713774 final TreeMountInfo mountInfo = getMountInfo ();
714775 final boolean hasMounted = mountInfo != null && mountInfo .hasMounted ;
0 commit comments