Anchored Custom Routes

How to implement a route which anchors visual elements to a Widget in the source route.

Gabriel Terwesten
Flutter Community
3 min readJul 12, 2020

--

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 Widgets, Elements, BuildContexts and RenderObjects, 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. RenderObjects give Elements 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 RenderObjects, 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 RenderObjects should only be accessed in this way from event handlers or animation listeners. RenderObjects 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

  • RenderObjects implement the visual representation of Widgets and contain layout information.
  • BuildContext.findRenderObject() can be used to access the RenderObject which visually represents an Element.
  • 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 and ModalRoute.buildTransitions are analogous to State.setState and State.build.
  • Stack and Positioned.fromRect are useful to position Widgets based on an anchor Widget in a custom route.

I hope this article proves helpful, even though some topics (like RenderObjects and transforms) are bit more advanced, and I’m happy to answer any questions and clarify/expand where necessary.

https://www.twitter.com/FlutterComm

--

--

Gabriel Terwesten
Flutter Community

Flutter • Kotlin • Spring • GraphQL • Software Engineer