How to make ListView preserve its scroll when transitioning to another route?

1,959

the way i created a "slide-out" animation in the question wasn't correct. in terms of the framework it's called a secondaryAnimation

to create your own secondary animation, you need to use a PageRouteBuilder transitionBuilder property

example can the code below, which produces such animation, and there's no problem with a ListView

enter image description here

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primaryColor: Colors.white),
      initialRoute: '/',
      onGenerateInitialRoutes: (initialRoute) => [createCustomTransition(HomeScreen())],
      onGenerateRoute: (settings) {
        if (settings.name == '1') {
          return createCustomTransition(SomeScreen());
        }
        return createCustomTransition(OtherScreen());
      },
      debugShowCheckedModeBanner: false,
    );
  }
}

/// Will create a custom route transition for you. 
PageRouteBuilder createCustomTransition(Widget screen) {
  return PageRouteBuilder(
    transitionDuration: const Duration(milliseconds: 700),
    reverseTransitionDuration: const Duration(milliseconds: 700),
    pageBuilder: (context, animation, secondaryAnimation) => screen,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      
      final slideAnimation = Tween(
        begin: const Offset(1.0, 0.0),
        end: Offset.zero,
      ).animate(CurvedAnimation(
        curve: Curves.easeOutCubic,
        reverseCurve: Curves.easeInCubic,
        parent: animation,
      ));

      final slideOutAnimation = Tween(
        begin: Offset.zero,
        end: const Offset(-0.3, 0.0),
      ).animate(CurvedAnimation(
        curve: Curves.easeOutCubic,
        reverseCurve: Curves.easeInCubic,
        parent: secondaryAnimation,
      ));

      return SlideTransition(
        position: slideAnimation,
        child: SlideTransition(
          position: slideOutAnimation,
          child: child,
        ),
      );
    },
  );
}

class HomeScreen extends StatelessWidget {
  HomeScreen({Key key}) : super(key: key);
  final List<int> list = List.generate(1000, (index) => index);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: ListView.builder(
                itemCount: list.length,
                itemBuilder: (context, index) => ListTile(
                  title: Center(
                    child: Text(list[index].toString()),
                  )
                ),
              )
            ),
            ElevatedButton(
              child: const Text('go to some screen'),
              onPressed: () {
                Navigator.of(context).pushNamed('1');
              },
            ),
          ],
        ),
      ),
    );
  }
}
class SomeScreen extends StatelessWidget {
  const SomeScreen({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.red,
      appBar: AppBar(),
      body: Center(
        child: ElevatedButton(
          child: const Text('go to other screen'),
          onPressed: () {
            Navigator.of(context).pushNamed('');
          },
        ),
      ),
    );
  }
}

class OtherScreen extends StatelessWidget {
  const OtherScreen({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue,
      appBar: AppBar(),
    );
  }
}

according to docs PageRouteBuilder is

A utility class for defining one-off page routes in terms of callbacks.

it's perfrect for general use, but if you are building something more complex, i suggest taking a look at some framework page route animation impementations and different class relationships they have

Share:
1,959
nt4f04und
Author by

nt4f04und

Updated on December 14, 2022

Comments

  • nt4f04und
    nt4f04und over 1 year

    I want to accomplish slide to right transition in my flutter application. The problem is that route transition kinda creates new instance of page that I want to transit from, and so ListView scroll resets.

    See a video

    That's how I create a new route

    /// @oldRoute needed cause this route transition utilizes `SlideStackRightRoute`
    Route createSettingsRoute(Widget oldRoute) {
      return SlideStackRightRoute(exitPage: oldRoute, enterPage: SettingsRoute());
    }
    

    And finally slide transition class itself

    import 'package:flutter/material.dart';
    
    /// Creates cupertino-like route transition, where new route pushes old from right to left
    class SlideStackRightRoute extends PageRouteBuilder {
      final Widget enterPage;
      final Widget exitPage;
      static var exBegin = Offset(0.0, 0.0);
      static var exEnd = Offset(-0.5, 0.0);
      static var entBegin = Offset(1.0, 0.0);
      static var entEnd = Offset.zero;
      static var curveIn = Curves.easeOutSine;
      static var curveOut = Curves.easeInSine;
    
      SlideStackRightRoute({@required this.exitPage, @required this.enterPage})
          : super(
              transitionDuration: Duration(milliseconds: 400),
              pageBuilder: (
                BuildContext context,
                Animation<double> animation,
                Animation<double> secondaryAnimation,
              ) =>
                  enterPage,
              transitionsBuilder: (
                BuildContext context,
                Animation<double> animation,
                Animation<double> secondaryAnimation,
                Widget child,
              ) =>
                  Stack(
                children: <Widget>[
                  SlideTransition(
                    position: Tween(begin: exBegin, end: exEnd)
                        .chain(CurveTween(curve: curveIn))
                        .chain(CurveTween(curve: curveOut))
                        .animate(animation),
                    child: Container(
                        foregroundDecoration: BoxDecoration(
                          color: Colors.black.withOpacity(animation.value / 2),
                        ),
                        child: exitPage),
                  ),
                  SlideTransition(
                    position: Tween(begin: entBegin, end: entEnd)
                        .chain(CurveTween(curve: curveIn))
                        .chain(CurveTween(curve: curveOut))
                        .animate(animation),
                    child: enterPage,
                  )
                ],
              ),
            );
    }