Flutter 导航推送,同时保持 Appbar 不变

Posted

技术标签:

【中文标题】Flutter 导航推送,同时保持 Appbar 不变【英文标题】:Flutter navigation push, while keeping the same Appbar 【发布时间】:2021-06-19 15:31:02 【问题描述】:

我目前正在构建一个 Flutter 应用程序,我正在努力找出实现导航的最佳方式。

我有 2 页,分别是:

主页:我想从那里使用 IndexedStack 来管理提要。 ProfilePage:个人资料页面,(以图形方式)与主页共享相同的 AppBar 和相同的 Drawer。

在我的应用程序中,用户在登录后立即到达主页。不涉及导航。

从那里,我现在有一个 TextButton,它调用 Navigator.of(context).pushNamed(AppRoutes.profile)

正如我所说,两个页面共享相同的 Appbar 和 Drawer,因此我创建了一个自定义 myScaffold。 两个页面都使用这个脚手架。

所以行为是正确的,因为在单击按钮后,ProfilePage 移动到了 HomePage 上。

我的问题是应用栏在图形上应该保持不变,但是当个人资料页面被推送时,动画清楚地表明它不是同一个应用栏。

是否可以为个人资料页面的条目设置动画,无需 动画应用栏的重建?

或者是否可以将路由直接推送到脚手架内容中?

作为替代方案,我只是想写一个函数 返回要在脚手架内容中显示的页面小部件。 但这种方法对我来说似乎不合适,因为有 路线。

从官方文档中,您可以从交互式示例中看到我的意思: Docs

当第二条路线建立在第一条路线之上时,新的 Appbar 会建立在前一条路线之上。 但是如果我需要 appbar 保持不变呢?

【问题讨论】:

【参考方案1】:

您可以使用Navigator 类创建子导航器。

我在当前项目中创建了一个路由库 (routes.dart),用于在仍显示 bottomNavigationBar 时导航到其他屏幕。使用相同的想法,您可以在使用相同的AppBar 的同时执行导航。

这是您的场景的示例代码。

ma​​in.dart

import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      navigatorKey: Routes.rootNavigatorKey,
      initialRoute: Routes.PAGE_INITIAL,
      onGenerateRoute: Routes.onGenerateRoute,
    );
  

routes.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter2sample/pages/home_page.dart';
import 'package:flutter2sample/pages/initial_page.dart';
import 'package:flutter2sample/pages/main_page.dart';
import 'package:flutter2sample/pages/profile_page.dart';

class Routes 
  Routes._();

  static const String PAGE_INITIAL = '/';
  static const String PAGE_MAIN = '/main';
  static const String PAGE_HOME = '/home';
  static const String PAGE_PROFILE = '/profile';

  static final GlobalKey<NavigatorState> rootNavigatorKey =
      GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> mainNavigatorKey =
      GlobalKey<NavigatorState>();

  static String currentSubNavigatorInitialRoute;

  static CupertinoPageRoute<Widget> onGenerateRoute(RouteSettings settings) 
    Widget page;

    switch (settings.name) 
      case PAGE_INITIAL:
        page = InitialPage();
        break;
      case PAGE_MAIN:
        page = MainPage();
        break;
      case PAGE_HOME:
        page = HomePage();
        break;
      case PAGE_PROFILE:
        page = ProfilePage();
        break;
    

    if (settings.name == PAGE_INITIAL &&
        currentSubNavigatorInitialRoute != null) 
      // When current sub-navigator initial route is set,
      // do not display initial route because it is already displayed.
      return null;
    

    return CupertinoPageRoute<Widget>(
      builder: (_) 
        if (currentSubNavigatorInitialRoute == settings.name) 
          return WillPopScope(
            onWillPop: () async => false,
            child: page,
          );
        

        return page;
      ,
      settings: settings,
    );
  

  /// [MaterialApp] navigator key.
  ///
  ///
  static NavigatorState get rootNavigator => rootNavigatorKey.currentState;

  /// [PAGE_MAIN] navigator key.
  ///
  ///
  static NavigatorState get mainNavigator => mainNavigatorKey.currentState;

  /// Navigate to screen via [CupertinoPageRoute].
  ///
  /// If [navigator] is not set, it will use the [rootNavigator].
  static void push(Widget screen, NavigatorState navigator) 
    final CupertinoPageRoute<Widget> route = CupertinoPageRoute<Widget>(
      builder: (_) => screen,
    );

    if (navigator != null) 
      navigator.push(route);
      return;
    

    rootNavigator.push(route);
  

  /// Navigate to route name via [CupertinoPageRoute].
  ///
  /// If [navigator] is not set, it will use the [rootNavigator].
  static void pushNamed(
    String routeName, 
    NavigatorState navigator,
    Object arguments,
  ) 
    if (navigator != null) 
      navigator.pushNamed(routeName, arguments: arguments);
      return;
    

    rootNavigator.pushNamed(routeName, arguments: arguments);
  

  /// Pop current route of [navigator].
  ///
  /// If [navigator] is not set, it will use the [rootNavigator].
  static void pop<T extends Object>(
    NavigatorState navigator,
    T result,
  ) 
    if (navigator != null) 
      navigator.pop(result);
      return;
    

    rootNavigator.pop(result);
  


//--------------------------------------------------------------------------------
/// A navigator widget who is a child of [MaterialApp] navigator.
///
///
class SubNavigator extends StatelessWidget 
  const SubNavigator(
    @required this.navigatorKey,
    @required this.initialRoute,
    Key key,
  ) : super(key: key);

  final GlobalKey<NavigatorState> navigatorKey;
  final String initialRoute;

  @override
  Widget build(BuildContext context) 
    final _SubNavigatorObserver _navigatorObserver = _SubNavigatorObserver(
      initialRoute,
      navigatorKey,
    );
    Routes.currentSubNavigatorInitialRoute = initialRoute;

    return WillPopScope(
      onWillPop: () async 
        if (_navigatorObserver.isInitialPage) 
          Routes.currentSubNavigatorInitialRoute = null;
          await SystemNavigator.pop();
          return true;
        

        final bool canPop = navigatorKey.currentState.canPop();

        if (canPop) 
          navigatorKey.currentState.pop();
        

        return !canPop;
      ,
      child: Navigator(
        key: navigatorKey,
        observers: <NavigatorObserver>[_navigatorObserver],
        initialRoute: initialRoute,
        onGenerateRoute: Routes.onGenerateRoute,
      ),
    );
  


//--------------------------------------------------------------------------------
/// [NavigatorObserver] of [SubNavigator] widget.
///
///
class _SubNavigatorObserver extends NavigatorObserver 
  _SubNavigatorObserver(this._initialRoute, this._navigatorKey);

  final String _initialRoute;
  final GlobalKey<NavigatorState> _navigatorKey;
  final List<String> _routeNameStack = <String>[];

  bool _isInitialPage = false;

  /// Flag if current route is the initial page.
  ///
  ///
  bool get isInitialPage => _isInitialPage;

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) 
    _routeNameStack.add(route.settings.name);
    _isInitialPage = _routeNameStack.last == _initialRoute;
  

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) 
    _routeNameStack.remove(route.settings.name);
    _isInitialPage = _routeNameStack.last == _initialRoute;
  

  @override
  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) 
    _routeNameStack.remove(route.settings.name);
    _isInitialPage = _routeNameStack.last == _initialRoute;
  

  @override
  void didReplace(Route<dynamic> newRoute, Route<dynamic> oldRoute) 
    _routeNameStack.remove(oldRoute.settings.name);
    _routeNameStack.add(newRoute.settings.name);
    _isInitialPage = _routeNameStack.last == _initialRoute;
  

initial_page.dart

import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';

class InitialPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: const Text('Initial Page'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            const Text('This is INITIAL page'),
            TextButton(
              onPressed: () => Routes.pushNamed(Routes.PAGE_MAIN),
              child: const Text('To Main page'),
            ),
          ],
        ),
      ),
    );
  

ma​​in_page.dart

import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';

class MainPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main Page'),
      ),
      body: SubNavigator(
        navigatorKey: Routes.mainNavigatorKey,
        initialRoute: Routes.PAGE_HOME,
      ),
    );
  

home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';

class HomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Colors.yellow,
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              const Text('This is HOME page'),
              TextButton(
                onPressed: () => Routes.pushNamed(
                  Routes.PAGE_PROFILE,
                  navigator: Routes.mainNavigator,
                ),
                child: const Text('To Profile page'),
              ),
            ],
          ),
        ),
      ),
    );
  

profile_page.dart

import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';

class ProfilePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Colors.grey,
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              const Text('This is PROFILE page'),
              TextButton(
                onPressed: () => Routes.pop(navigator: Routes.mainNavigator),
                child: const Text('Back to Home page'),
              ),
            ],
          ),
        ),
      ),
    );
  

【讨论】:

以上是关于Flutter 导航推送,同时保持 Appbar 不变的主要内容,如果未能解决你的问题,请参考以下文章

Flutter——AppBar组件(顶部导航组件)

Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航

Flutter:如何在抽屉内推送/弹出导航器页面?

Flutter appBar后退按钮不出现

[Flutter]专题Flutter 中的 AppBar详解#yyds干货盘点#

appbar导航