opaqueRouteCoverAbove function
Area (root coordinate space, logical pixels) that an opaque route above
the route of context paints over, or null when nothing opaque is painted
above it.
Masks are drawn by native on top of the finished frame, so a widget that is still laid out but hidden behind a page sliding over it would get a mask on top of that page. The Overlay keeps the covered route onstage for the whole transition (and again from the first frame of a pop, when the page on top has not moved yet), so the widget's own visibility cannot tell.
Only a cover that is certainly opaque is reported: the route must be ModalRoute.opaque and the render objects between its page and the overlay must be known to paint the page as laid out, with no opacity below 1. A fading, snapshot based (default Android Zoom) or otherwise unknown transition returns null and leaves the mask in place, which is never worse than not knowing about the cover at all.
Implementation
Rect? opaqueRouteCoverAbove(BuildContext context) {
final ModalRoute<dynamic>? route = ModalRoute.of(context);
final NavigatorState? navigator = route?.navigator;
final OverlayState? overlay = navigator?.overlay;
if (route == null || navigator == null || overlay == null) {
return null;
}
// Overlay builds a single _Theatre whose children are the onstage entries,
// bottom to top.
Element? theatre;
overlay.context.visitChildElements((Element e) => theatre = e);
final Element? theatreElement = theatre;
if (theatreElement == null) {
return null;
}
final Set<Element> ancestors = <Element>{};
context.visitAncestorElements((Element e) {
ancestors.add(e);
return !identical(e, theatreElement);
});
final List<Element> entries = <Element>[];
theatreElement.visitChildElements(entries.add);
final int own = entries.indexWhere(ancestors.contains);
if (own < 0 || own == entries.length - 1) {
return null;
}
final RenderObject? theatreRender = theatreElement.renderObject;
for (int i = entries.length - 1; i > own; i--) {
final ModalRoute<dynamic>? above = _routeOfOverlayEntry(entries[i]);
if (above == null ||
identical(above, route) ||
above.navigator != navigator ||
!above.opaque) {
continue;
}
final RenderObject? page = above.subtreeContext?.findRenderObject();
if (page == null || !_paintsOpaquely(page, theatreRender)) {
continue;
}
final Rect? cover = maskGeometryOf(page)?.visible;
if (cover == null || cover.isEmpty) {
continue;
}
return cover;
}
return null;
}