持久化底部导航栏

Flutter 一个高度可定制的持久化底部导航栏

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

persistent

样式

样式15 样式16
style-15 style-16
样式1 样式9
style-1 style-9
样式7 样式10
style-7 style-10
样式12 样式13
style-12 style-13
样式3 样式6
style-3 style-6
Neumorphic 无副标题的拟态风格
neumorphic neumorphic-nosubs

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

功能

  • 高度可定制的持久化底部导航栏。
  • 能够推送新屏幕,无论是否带有底部导航栏。
  • 底部导航栏的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上,它会保留底部导航栏。这是每个平台指定的默认行为。


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


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

如果您正在推送新的模态屏幕,请使用以下函数:


    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

https://github.com/BilalShahid13/PersistentBottomNavBar