jonahwilliams/flutter_shaders

Page Transitions

rivella50 opened this issue · 7 comments

Hi there,

i would like to implement outstanding page transitions for my current Flutter game and was wondering if your package could be used for using e.g. this shader as a page transition between two pages in the transitionsBuilder part of a PageRouteBuilder?

My idea is to wrap the target page with AnimatedBuilder->ShaderBuilder->AnimatedSampler where the shader could output values for finally showing that page to the user.

Would that somehow be possible? Or do you have another idea how page transitions using shaders could be realized?
Thank you very much.

I think I have some code around here somewhere that makes it easy to use a shader with a page transition builder, let me see if I can find it. That would be a reasonable addition to this package.

That would be awesome. I was trying something like this but at the moment i only get back a black screen when trying to go to Page2:

Route _createRoute(BuildContext context) {
  Page2 targetPage = const Page2();

  return PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => targetPage,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        return TweenAnimationBuilder(
          tween: Tween(begin: 0.0, end: 1.0),
          duration: Duration(milliseconds: 500),
          curve: Curves.easeIn,
          child: child,
          builder: (BuildContext context, double value, Widget? child) {
            return ShaderBuilder(
              (context, shader, _) {
                return AnimatedSampler(
                  (image, size, canvas) {
                    shader.setFloat(0, 20);
                    shader.setFloat(1, value);
                    shader.setFloat(2, size.width);
                    shader.setFloat(3, size.height);
                    shader.setImageSampler(0, image);
                  },
                  child: targetPage,
                );
              },
              assetKey: 'assets/shaders/shader.frag',
            );
          },
        );
      }
    );
}

Ok i got it working for the target page:
https://github.com/jonahwilliams/flutter_shaders/assets/54292/5b18403a-e232-445e-a822-1c7beb0ded43

The only missing thing is having access to the from page image in order to do decisive renderings like this in the .frag file:

fragColor = nextImage ? texture(toImage, uv) : texture(fromImage, uv);

This is where an update of your package could help. I have something like this in mind:

return AnimatedSamplerForTransition(
  (fromImage, toImage, size, canvas) {
    // perform all shader inputs
    shader.setImageSampler(0, fromImage);
    shader.setImageSampler(1, toImage);
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.width, size.height),
      Paint()..shader = shader,
    );
  },
  from: context.widget,  // represents the currently shown page
  to: targetPage,
);

Ahh I think I remember why I was having problems with this. Its fairly easy to run a shader over a single page as part of a page transition, but getting both pages requires some cooperation from the theme objects. let me think about this one for a bit.

Ok, i will also continue my investigations.
From what i've seen is that e.g. ZoomPageTransitionsBuilder can take snapshots for its routes by using SnapshotWidget. Perhaps this could be an approach in order to get the image from the current page.

I got it working:
https://github.com/jonahwilliams/flutter_shaders/assets/54292/10013873-4764-4d1f-879a-da8024003543

The caveat is that the page which serves as the from page needs to be wrapped with a RepaintBoundary and a GlobalKey being applied to its key field.

// build method of the from page
@override
Widget build(BuildContext context) {
  return RepaintBoundary(
    key: scr,
    child: Scaffold(
    ...

With that a ui.Image can be taken by calling this method:

final scr = GlobalKey();
ui.Image _takeSnapshot() {
  RenderRepaintBoundary b = scr.currentContext!.findRenderObject() as RenderRepaintBoundary;
  return b.toImageSync();
}

and used as the ImageSampler input for the shader.

So in general it would work, but if you have a better solution which doesn't need the wrapping with RepaintBoundary (perhaps this could be done ad hoc?) that would be great.

EDIT: Using this method (slightly modified) from Screenshot package it works without wrapping the from page with RepaintBoundary.

EDIT2: After further tests i've seen that using the method from EDIT doesn't work with a StatefulWidget, since that widget will be recreated which loses its state (that's probably what you meant with cooperation from the theme objects) and results in a image with the widget's initial state. I haven't found yet a solution which allows keeping that state when trying to generate an image (except of course wrapping the from page widget with RepaintBoundary).

Hi Jonah,
so for the moment i ended up with this solution:

  • using your package in order to simplify the incorporation of shaders for page transitions incl. receiving an ui.Image of the target page
  • using the Screenshot package in order to getting a wrapper for the RepaintBoundary/GlobalKey stuff which helps receiving an ui.Image of the from page

Of course 2 packages are needed to accomplish this - which is ok for me.
If you still think that extending your package with page transitions support would be a reasonable idea then you could try to include the RepaintBoundary/GlobalKey functionality (or a similar approach) in your package where as a result the builder callback for AnimatedSampler (or a variant of it for transitions) then could offer both the from and target ui.Image, which would make the Screenshot package obsolete.

I'm looking forward to your opinion.