Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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,
              )
            ],
          ),
        );
}

like image 354
nt4f04und Avatar asked Oct 13 '19 01:10

nt4f04und


1 Answers

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

  • widgets/page_transitions_theme.dart - various page route builders
  • material/route - android routes implementation
  • cupertino/route - cupertion routes implementation
like image 60
nt4f04und Avatar answered Nov 27 '22 17:11

nt4f04und