Shared Element Transition in Flutter

by Dec 31, 2018Flutter5 comments

Hello there! Some time ago a friend of mine showed me this article (preview below) and asked me if I can do Shared Element Transitions in Flutter. The first thought that came into my mind was “Simple, Heroes!” but when I looked closely, I’ve noticed that the transition in that article occurs in one page, so Hero transitions wouldn’t work in that case. In this post I will use AnimationControllers, Overlays and Rects to get the same result as below:

Expected result

Setup and GridView

Let’s start with adding images to the project. All we need to do is copy the images into assets directory. You can find all of them here.

After that, let’s update pubspec.yaml file, so that Flutter will be able to find those images.

Now all that’s left is creating a HomePage widget with a GridView:

The app should look like this:

. . .

Adding a PageView

Now we need to add a PageView. Since we need to keep track of pages, I want the page view to be rendered all the time. On the other hand, I want it to be visible on top of the GridView only when the GridView element was clicked. To achieve such a result, we need to use a combination of StackOpacity and IgnorePointer widgets.

So what is happening now:

  • When a user clicks a GridView card, we set isPageViewVisible to true.
  • When a user presses the back button, if the pageView is visible, we set the flag to false.
  • PageView is always on top of the GridView.
  • If isPageViewVisible is false, then PageView is transparent (Opacity) and unclickable (IgnorePointer) so it behaves the same as it wasn’t even there.

Let’s see how it looks:

. . .

Syncing PageView and GridView

Right now we can show and hide PageView but it has nothing to do to on what picture we pressed or when we pressed back. We need to have 2 way syncing:

  • On GridView click, the corresponding image in PageView should be displayed.
  • When PageView gets hidden, it should scroll GridView to the last displayed image.

The first part is simple, all we need to do is add PageController, which has method jumpTo. We can use this method to set the page we want, just like this:

From now on, on every click, PageView starts from clicked image:

Now it’s time for the harder part: scrolling to the picture on PageView closed. Similarly to the previous example, we will use ScrollController which can be attached to the GridView. Unfortunately, ScrollControllers don’t have jumpTo(index) method, you can only pass the scroll offset. To overcome this, we need to calculate the offset at which the specific picture is displayed. Let’s take a look at the following method:

Luckily, we know the cards’ height as it is half of the screen width. Having that, we can easily deduct how much we should scroll.

To use that method, all we need to do is add a new ScrollController to our state class:

The effect? Hiding the PageView causes GridView to scroll:

. . .

Transition

Getting Rect objects

Now it’s time to work on the transition from GridView image to PageView image and backward. To do that, we need to know the position of both Widgets. We could use context.findRenderObject method and figure it out, but because I am a lazy man, I am going to use DebuggerX‘s awesome rect_getter package from Pub which gives easy access to Widgets’ Rects.

The usage of this package is very simple, we wrap with RectGetter widget, pass a key and then use the same key to get a Rect object describing the size and position of the wrapped widget. Let’s put it into our code:

What we’ve done here is creating 2 lists of keys for grid images and page images. Each key was passed to specific RectGetter. We also started the implementation of the transition method, where we need to access both gridRect and pageRect. Notice that I added Future.delayed before starting transitions. The problem is that if the transition starts just after jumpTo methods, it may not get the latest position of images. This solution is more a workaround, if you know a better way to do it, please let me know! ?

Animated image

To create the animation we will need two main components: Animations and Overlays. Let’s start with the animation:

We have our AnimationController and rectAnimation which will animate from the selected grid image to the same page image. Depending on the direction (from the grid to a page or from page to grid) we decide if we should run forward() or reverse(). But animation itself won’t make any difference if we don’t use it to build Widgets. To do that we will use Overlay.

The overlay is a handy Widget if we want our Widgets to float on top of others. It has its own Stack, in which we can put OverlayEntries. Our OverlayEntry will be simple widget wrapped in AnimatedBuilder so that it is built every time our animation changes. Inside that, we will have an Image wrapped in Positioned. We only need to also make sure that the image’s position and size are dependent on the current rectAnimation value. The code looks like this:

The only thing left that keeps us from seeing beautiful shared element transition is using that overlay entry. All we need to do is add it do Overlay when the transition is about to start and remove it when it’s finished:

We’ve also added state management to be depended on Animation status, if AnimatedController is completed, we set isPageViewVisible to true, when it starts to go back, we change it to false.

It’s time to see how it all looks like:

Animated curtain

The last part is to make the GridView fade into white, while the animation progresses. Luckily, we are already using Stack, so it won’t be a problem:

. . .

And that’s it!

I hope you enjoyed this post and from now on the shared element transitions won’t be a problem for you. ?

If you have any questions, feel free to leave a comment!

You can see the full code here.

Cheers

Marcin Szałek

Founder of Fidev

Flutter enthusiast since Alpha release in 2017. Believes that sharing is caring, which lead him to start a technical blog dedicated fo Flutter in its early days. Loves to see beautiful designs become real apps and is willing to help make it happen. Enjoys sunny beaches far from home.

Join the newsletter!

Join the newsletter to keep track with latest posts and get my special Widget to animate views' entrances without any hassle for FREE!

Check out other posts!

Stripe Checkout in Flutter Web

Stripe Checkout in Flutter Web

Flutter Web is getting more mature every day. If you want to accept payments using Stripe Checkout in your Flutter web application, this article is just for you!

Stripe Checkout in mobile Flutter app

Stripe Checkout in mobile Flutter app

Have you ever struggled to integrate card payments into your mobile Flutter app? If so, today is your lucky day! In this post, I present how to use Stripe Checkout in the Flutter app without any hassle!

Interacting with Widgets using Framy

Interacting with Widgets using Framy

Have you ever developed a widget or a page and you wanted to make sure it works correctly in different scenarios but then it turned out that you can’t just reproduce all the cases you want to cover? Framy may solve such problems!

Share This

Share This

Share this post with your friends!