Anchored Custom Routes
How to implement a route which anchors visual elements to a Widget in the source route.
Having watched Marcin Szałek’s excellent talk on complex UIs in Flutter, I was looking for some interesting UI interactions to experiment with and found this shot on dribbble. What caught my eye was the screen transition, which reveals a new screen by expanding a circular clip.
Here is what it looks like in Flutter:
If you’re interested, this route is available on pub.dev (circular_clip_route).
While implementing the route, I found that a key problem was anchoring the transition animation to the Widget
which pushes the route, but the solution is useful in general, for example for menus (like PopupMenuButton
) or tooltips.
Locating the Anchor Widget in the Destination Route
To position anything relative to an anchor Widget
inside the destination route, we need the geometry of the anchor Widget
in the coordinate system of the destination route.
We’re going to need to understand the relationship between Widget
s, Element
s, BuildContext
s and RenderObject
s, so let’s briefly recap. A Widget
is a configuration for an Element
. An Element
is an instance of a Widget
(at a specific position in the Element
tree). Element
implements BuildContext
. RenderObject
s give Element
s their visual representation. BuildContext.findRenderObject()
can be used to find the RenderObject
which visually represents that BuildContext
/Element
.
To create a transform matrix from the coordinate space of the anchor Widget
to that of the destination route, we need the anchor Widget
’s BuildContext
and the BuildContext
of the Navigator
which contains the destination route. A route implementation can get the latter through Route.navigator.context
.
To access the anchor Widget
’s BuildContext
, either wrap the anchor Widget
in a Builder
or pass it a GlobalKey
.
Now that we know how to get the RenderObject
s, we can use them to calculate the geometry of the anchor Widget
, in the destination route’s coordinate system.
As a side note, it is important to understand that RenderObject
s should only be accessed in this way from event handlers or animation listeners. RenderObject
s need to have been laid out in a previous frame and not be dirty, which for example is not guarantied during the build phase. For custom route implementations, Route.didPush
and Route.didPop
are good candidates to override and calculate any required geometry.
Implementing OverlayRoute
The custom route below demonstrates a minimal example of how to implement a route, which overlays its content on top of an anchor
. In and of itself this is not a very useful route, but it demonstrates all the important parts. For simplicity there are no transition animations. If you’re interested in a more complex example, take a look at my implementation of the earlier mentioned circular_clip_route. It’s a bigger chunk of code, but I’ve tried to explain what each part does and why it is there, in the comments.
Key Takeaways
RenderObject
s implement the visual representation ofWidget
s and contain layout information.BuildContext.findRenderObject()
can be used to access theRenderObject
which visually represents anElement
.RenderObject.getTransformTo(null)
returns a transform from the local to the global coordinate system. If you are interested in what the parameter represents, take a look at the linked docs.- Only access layout information in event handlers and animation listeners.
ModalRoute.setState
andModalRoute.buildTransitions
are analogous toState.setState
andState.build
.Stack
andPositioned.fromRect
are useful to positionWidget
s based on an anchorWidget
in a custom route.
I hope this article proves helpful, even though some topics (like RenderObject
s and transforms) are bit more advanced, and I’m happy to answer any questions and clarify/expand where necessary.