Заменить начальный маршрут в MaterialApp без анимации?

Наше приложение построено на основе Scaffold и к этому моменту мы смогли удовлетворить большинство наших требований к маршрутизации и навигации, используя предоставляемые вызовы в NavigatorState (pushNamed(), pushReplacementNamed() и т.д.). Чего мы не хотим, так это иметь какую-либо анимацию "толчка", когда пользователь выбирает элемент из нашего меню. Мы хотим, чтобы экран назначения из щелчка навигационного меню стал новым начальным маршрутом стека. На данный момент мы используем pushReplacementNamed() для этого, чтобы гарантировать отсутствие стрелки назад на панели приложения. Но анимация скольжения справа показывает, что строится стек.

Каков наш лучший вариант для изменения этого начального маршрута без анимации, и можем ли мы сделать это, одновременно одновременно анимируя закрытый ящик? Или мы смотрим на ситуацию, когда нам нужно перейти от Navigator к использованию только одного Scaffold и обновлять "body" напрямую, когда пользователь хочет сменить экран?

Мы отмечаем, что в NavigatorState вызов replace() который, как мы полагаем, может быть подходящим местом для начала поиска, но неясно, как получить доступ к нашим различным маршрутам, изначально настроенным в new MaterialApp(). Что-то вроде replaceNamed() может быть в порядке ;-)

Ответ 1

То, что вы делаете, похоже на BottomNavigationBar, поэтому вы можете рассмотреть один из них, а не Drawer.

Тем не менее, наличие единого Scaffold и обновление body когда пользователь забирает элемент ящика, является вполне разумным подходом. Вы можете подумать о FadeTransition из одного тела в другое.

Или, если вам нравится использовать Navigator но не хотите слайд-анимацию по умолчанию, вы можете настроить (или отключить) анимацию, расширив MaterialPageRoute. Вот пример этого:

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyCustomRoute<T> extends MaterialPageRoute<T> {
  MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
      : super(builder: builder, settings: settings);

  @override
  Widget buildTransitions(BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    if (settings.isInitialRoute)
      return child;
    // Fades between routes. (If you don't want any animation, 
    // just return child.)
    return new FadeTransition(opacity: animation, child: child);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Navigation example',
      onGenerateRoute: (RouteSettings settings) {
        switch (settings.name) {
          case '/': return new MyCustomRoute(
            builder: (_) => new MyHomePage(),
            settings: settings,
          );
          case '/somewhere': return new MyCustomRoute(
            builder: (_) => new Somewhere(),
            settings: settings,
          );
        }
        assert(false);
      }
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Navigation example'),
      ),
      drawer: new Drawer(
        child: new ListView(
          children: <Widget> [
            new DrawerHeader(
              child: new Container(
                  child: const Text('This is a header'),
              ),
            ),
            new ListTile(
              leading: const Icon(Icons.navigate_next),
              title: const Text('Navigate somewhere'),
              onTap: () {
                Navigator.pushNamed(context, '/somewhere');
              },
            ),
          ],
        ),
      ),
      body: new Center(
        child: new Text(
          'This is a home page.',
        ),
      ),
    );
  }
}

class Somewhere extends StatelessWidget {
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new Text(
          'Congrats, you did it.',
        ),
      ),
      appBar: new AppBar(
        title: new Text('Somewhere'),
      ),
      drawer: new Drawer(
        child: new ListView(
          children: <Widget>[
            new DrawerHeader(
              child: new Container(
                child: const Text('This is a header'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Ответ 2

Спасибо за невероятно полезный ответ, Коллин!

Небольшой мод, который иногда может быть полезен: по другим причинам мне нужна анимация с затуханием, когда мой экран (Scaffold) входил, но он был в стеке, и, когда я выходил, мне хотелось, чтобы обычный слайд вниз, так как он был добавлен Navigator.pushReplacement(...). Невероятно легко добиться этого:

class FadeInSlideOutRoute<T> extends MaterialPageRoute<T> {
  FadeInSlideOutRoute({WidgetBuilder builder, RouteSettings settings})
      : super(builder: builder, settings: settings);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    if (settings.isInitialRoute) return child;
    // Fades between routes. (If you don't want any animation,
    // just return child.)
    if (animation.status == AnimationStatus.reverse)
      return super.buildTransitions(context, animation, secondaryAnimation, child);
    return FadeTransition(opacity: animation, child: child);
  }
}

Ответ 3

Вы также можете использовать PageRouteBuilder для этого.

Пример:

@override
Widget build(BuildContext context) {
  return RaisedButton(
    onPressed: () {
      Navigator.push(
        context,
        PageRouteBuilder(
          pageBuilder: (context, anim1, anim2) => SecondScreen(),
          transitionsBuilder: (context, anim1, anim2, child) => FadeTransition(opacity: anim1, child: child),
          transitionDuration: Duration(seconds: 1),
        ),
      );
    },
  );
}

И если вы не хотите иметь анимацию, замените вышеупомянутые transitionsBuilder на

transitionsBuilder: (context, anim1, anim2, child) => Container(child: child),