持久底部导航栏

pub package version license github stars

Flutter 的持久/静态底部导航栏。

注意:从 2.0.0 版本之前迁移的用户应查看最新的 Readme 和说明,因为 2.0.0 更新引入了许多重大更改。

Persistent Behavior

样式

样式15 样式16
style1 style10
样式1 样式9
style1 style10
样式7 样式10
style3 style5
样式12 样式13
style6 style8
样式3 样式6
style6 style8
Neumorphic 仿拟风格,无副标题
neumorphic1 neumorphic2

注意:这些不包含所有样式变体。

功能

  • 高度可定制的“持久”底部导航栏。
  • 能够推入新屏幕,带或不带底部导航栏。
  • 20种底部导航栏样式。
  • 包含用于推入屏幕(带或不带底部导航栏)的函数,即 `pushNewScreen()` 和 `pushNewScreenWithRouteSettings()`。
  • 基于 Flutter 的 Cupertino (iOS) 底部导航栏。
  • 对于特定标签页,可以是“半透明”的。
  • 自定义导航栏样式。点击 此处 了解更多信息。
  • 处理 Android 硬件/软件返回按钮。

入门

在您的 Flutter 项目中添加依赖项

dependencies:
  persistent_bottom_nav_bar: any

导入包

import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart';

持久底部导航栏使用 `PersistentTabController` 作为其控制器。以下是声明它的方法:

PersistentTabController _controller;

_controller = PersistentTabController(initialIndex: 0);

然后声明的主要小部件是 `PersistentTabView`。注意:此小部件包含 SCAFFOLD (基于 `CupertinoTabScaffold`),因此无需声明它。以下是一个用于演示的示例:

class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PersistentTabView(
        context,
        controller: _controller,
        screens: _buildScreens(),
        items: _navBarsItems(),
        confineInSafeArea: true,
        backgroundColor: Colors.white, // Default is Colors.white.
        handleAndroidBackButtonPress: true, // Default is true.
        resizeToAvoidBottomInset: true, // This needs to be true if you want to move up the screen when keyboard appears. Default is true.
        stateManagement: true, // Default is true.
        hideNavigationBarWhenKeyboardShows: true, // Recommended to set 'resizeToAvoidBottomInset' as true while using this argument. Default is true.
        decoration: NavBarDecoration(
          borderRadius: BorderRadius.circular(10.0),
          colorBehindNavBar: Colors.white,
        ),
        popAllScreensOnTapOfSelectedTab: true,
        popActionScreens: PopActionScreensType.all,
        itemAnimationProperties: ItemAnimationProperties( // Navigation Bar's items animation properties.
          duration: Duration(milliseconds: 200),
          curve: Curves.ease,
        ),
        screenTransitionAnimation: ScreenTransitionAnimation( // Screen transition animation on change of selected tab.
          animateTabTransition: true,
          curve: Curves.ease,
          duration: Duration(milliseconds: 200),
        ),
        navBarStyle: NavBarStyle.style1, // Choose the nav bar style with this property.
    );
  }
}

    List<Widget> _buildScreens() {
        return [
          MainScreen(),
          SettingsScreen()
        ];
    }

    List<PersistentBottomNavBarItem> _navBarsItems() {
        return [
            PersistentBottomNavBarItem(
                icon: Icon(CupertinoIcons.home),
                title: ("Home"),
                activeColorPrimary: CupertinoColors.activeBlue,
                inactiveColorPrimary: CupertinoColors.systemGrey,
            ),
            PersistentBottomNavBarItem(
                icon: Icon(CupertinoIcons.settings),
                title: ("Settings"),
                activeColorPrimary: CupertinoColors.activeBlue,
                inactiveColorPrimary: CupertinoColors.systemGrey,
            ),
        ];
    }

导航器函数

注意:您仍然可以使用常规的 Navigator 函数,例如 ‘pushNamed’,但请务必检查 `PersistentBottomNavBarItem` 中的 `routeAndNavigatorSettings` 参数以获取路由设置和其他一些与导航器相关的属性。 要推入新屏幕,请使用以下函数来控制特定屏幕上底部导航栏的“可见性”。您可以使用自己的逻辑来实现“特定平台”的行为。一种解决方案是使用 `withNavBar` 属性并根据平台进行切换。

在“特定平台”行为中,当推入新屏幕时,在 Android 上它将不带底部导航栏推入屏幕,但在 iOS 上它将保留底部导航栏。这是每个平台指定的默认行为。

    PersistentNavBarNavigator.pushNewScreen(
        context,
        screen: MainScreen(),
        withNavBar: true, // OPTIONAL VALUE. True by default.
        pageTransitionAnimation: PageTransitionAnimation.cupertino,
    );

    PersistentNavBarNavigator.pushNewScreenWithRouteSettings(
        context,
        settings: RouteSettings(name: MainScreen.routeName),
        screen: MainScreen(),
        withNavBar: true,
        pageTransitionAnimation: PageTransitionAnimation.cupertino,
    );

如果您要推入新的“模态”屏幕,请使用以下函数:

    PersistentNavBarNavigator.pushDynamicScreen(
        context,
        screen: HomeModalScreen(),
        withNavBar: true,
    );

一些有用的提示

  • 弹出到给定标签页导航图中的任何屏幕

        Navigator.of(context).popUntil((route) {
            return route.settings.name == "ScreenToPopBackTo";
        });
  • 弹出到给定标签页导航图中的第一个屏幕

        Navigator.of(context).popUntil(ModalRoute.withName("/"));

        Navigator.of(context).pushAndRemoveUntil(
          CupertinoPageRoute(
            builder: (BuildContext context) {
              return FirstScreen();
            },
          ),
          (_) => false,
        );
  • 要将底部工作表推到导航栏之上,请使用 `showModalBottomScreen` 并将其属性 `useRootNavigator` 设置为 true。请参阅示例项目以获得说明。

自定义导航栏样式

如果您想要自己的导航栏样式,请遵循以下步骤:

  1. 声明您的自定义小部件。请牢记,您需要自己处理 `onSelectedItem` 函数和 `selectedIndex` 整数以保持全部功能。另请注意,您可以定义自己的导航栏项模型,而不是使用提供的 `PersistentBottomNavBarItem`。请参阅下面的示例以更好地理解。

        class CustomNavBarWidget extends StatelessWidget {
            final int selectedIndex;
            final List<PersistentBottomNavBarItem> items; // NOTE: You CAN declare your own model here instead of `PersistentBottomNavBarItem`.
            final ValueChanged<int> onItemSelected;
    
            CustomNavBarWidget(
                {Key key,
                this.selectedIndex,
                @required this.items,
                this.onItemSelected,});
    
            Widget _buildItem(
                PersistentBottomNavBarItem item, bool isSelected) {
                return Container(
                alignment: Alignment.center,
                height: 60.0,
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                    Flexible(
                        child: IconTheme(
                        data: IconThemeData(
                            size: 26.0,
                            color: isSelected
                                ? (item.activeColorSecondary == null
                                    ? item.activeColorPrimary
                                    : item.activeColorSecondary)
                                : item.inactiveColorPrimary == null
                                    ? item.activeColorPrimary
                                    : item.inactiveColorPrimary),
                        child: item.icon,
                        ),
                    ),
                    Padding(
                        padding: const EdgeInsets.only(top: 5.0),
                        child: Material(
                        type: MaterialType.transparency,
                        child: FittedBox(
                            child: Text(
                            item.title,
                            style: TextStyle(
                                color: isSelected
                                    ? (item.activeColorSecondary == null
                                        ? item.activeColorPrimary
                                        : item.activeColorSecondary)
                                    : item.inactiveColorPrimary,
                                fontWeight: FontWeight.w400,
                                fontSize: 12.0),
                        )),
                        ),
                    )
                    ],
                ),
                );
            }
    
            @override
            Widget build(BuildContext context) {
                return Container(
                color: Colors.white,
                child: Container(
                    width: double.infinity,
                    height: 60.0,
                    child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: items.map((item) {
                        int index = items.indexOf(item);
                        return Flexible(
                        child: GestureDetector(
                            onTap: () {
                            this.onItemSelected(index);
                            },
                            child: _buildItem(
                                item, selectedIndex == index),
                        ),
                        );
                    }).toList(),
                    ),
                ),
                );
            }
        }
    
  2. 在主 `PersistentTabView` 小部件中,将 `navBarStyle` 属性设置为 `NavBarStyle.custom`,并将您刚刚创建的自定义小部件通过 `customWidget` 属性传递进来,如下所示:

    class MyApp extends StatelessWidget {
        const MyApp({Key key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
            return PersistentTabView.custom(
                context,
                controller: _controller,
                itemCount: items.length, // This is required in case of custom style! Pass the number of items for the nav bar.
                screens: _buildScreens(),
                confineInSafeArea: true,
                handleAndroidBackButtonPress: true,
                onItemSelected: (int) {
                    setState(() {}); // This is required to update the nav bar if Android back button is pressed
                },
                customWidget: CustomNavBarWidget( // Your custom widget goes here
                    items: _navBarsItems(),
                    selectedIndex: _controller.index,
                    onItemSelected: (index) {
                        setState(() {
                            _controller.index = index; // NOTE: THIS IS CRITICAL!! Don't miss it!
                        });
                    },
                ),
            );
        }
    }
    

    注意:在 `customWidget` 的 `onSelected` 函数中,不要忘记更改控制器的索引。

  3. 完成!如我们所见,其他一些属性,如 `iconSize`、`items` 等在这里不是必需的,因此您可以跳过这些属性。要控制屏幕的“底部填充”,请使用 `bottomScreenPadding`。如果您设置了过多的 `bottomScreenPadding` 但自定义小部件中的高度较低,或者反之亦然,可能会出现布局问题。

为了更好地理解,请参阅官方 git 仓库中的 示例项目

GitHub

查看 Github