一、Scalfold 页面结构

之前 Flutter 基本页面结构布局 及 自定义 Widget 文件 分离 这篇文章提到了 Scalfold Widget 是用来描述页面的主结构

一个 MaterialApp 由多个 Scalfold 页面组成,每个 Scalfold 的普遍结果如下:

  • AppBar:顶部导航栏
  • body:中间内容体
  • BottomNavigationBar:底部导航栏

当然除了上面,还有 drawerfloatingButton

这些都是可以省略的,这点从 Scalfold 构造函数就能看出:

  const Scaffold({
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.drawerScrimColor,
  }) : assert(primary != null),
       assert(extendBody != null),
       assert(drawerDragStartBehavior != null),
       super(key: key);

关于 AppBar 和 body 可以看上面的文章怎么使用,不再重复,这里主要是是 bottomNavigationBar 的配置和使用

bottomNavigationBar 的类型是 Widget BottomNavigationBar,构造函数如下:

  BottomNavigationBar({
    Key key,
    @required this.items,
    this.onTap,
    this.currentIndex = 0,
    this.elevation = 8.0,
    BottomNavigationBarType type,
    Color fixedColor,
    this.backgroundColor,
    this.iconSize = 24.0,
    Color selectedItemColor,
    this.unselectedItemColor,
    this.selectedIconTheme = const IconThemeData(),
    this.unselectedIconTheme = const IconThemeData(),
    this.selectedFontSize = 14.0,
    this.unselectedFontSize = 12.0,
    this.selectedLabelStyle,
    this.unselectedLabelStyle,
    this.showSelectedLabels = true,
    bool showUnselectedLabels,
  })

从上面看出,items 是必填的属性参数,也就是一个 BottomNavigationBarItem Widget 列表,定义如下:

  /// Defines the appearance of the button items that are arrayed within the
  /// bottom navigation bar.
  final List<BottomNavigationBarItem> items;

其他几个经常用到的参数是:

  • onTap: 处理 tab 的点击事件
  • currentIndex:当前那个 tab 是 active 状态的

还有都是指定样式的了

二、BottomNavigationBar 实现底部 tab 导航

1、准备三个页面

首先 Tab 切换 切换的是页面,因此我们需要准备几个 Page,几个 Page 就很简单,就是显示文字

1)首页

// 首页页面
class HomePage extends StatelessWidget {
  const HomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PageContent(title: '首页');
  }
}

2)新闻页

// 新闻页面
class NewsPage extends StatelessWidget {
  const NewsPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PageContent(title: '新闻');
  }
}

最终放到一个列表中:

  List<Widget> _pageList = [
    HomePage(),
    NewsPage(),
    MyPage(),
  ];

3)我的

// 我的页面
class MyPage extends StatelessWidget {
  const MyPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PageContent(title: '我的');
  }
}

2、配置 BottomNavigationBar.items

每个 Tab 子项和我们的页面是一一对应的,三个页面需要构造三个 BottomNavigationBarItem

BottomNavigationBarItem 构造函数很简单:

  const BottomNavigationBarItem({
    @required this.icon,
    this.title,
    Widget activeIcon,
    this.backgroundColor,
  }) : activeIcon = activeIcon ?? icon,
       assert(icon != null);

iconactiveIcon 是两个状态下的图标,title 则是文本,只有默认的 icon 是必须的。

bottomNavigationBar.items 可以构造如下:

  List<BottomNavigationBarItem> _barItem = [
    BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
    BottomNavigationBarItem(icon: Icon(Icons.list), title: Text('新闻')),
    BottomNavigationBarItem(icon: Icon(Icons.people), title: Text('我的')),
  ];

3、配置 Scalfold.bodyScalfold.bottomNavigationBar

有了 bottomNavigationBar 的列表之后,我们直接进行配置,除此之外,我们需要一个变量来存储当前是访问的第几个 tab: int _currentIndex = 0;

currentIndex: _currentIndex,
items: _barItem,
fixedColor: Colors.pink,
selectedFontSize: 12,
type: BottomNavigationBarType.fixed,

配置好 itemscurrentIndex 属性之外,我们需要处理点击事件 onTap

          onTap: (int index) {
            setState(() {
              this._currentIndex = index;
            });
          },

每次点击,我们都动态的切换当前的 index,也就是 currentIndex,因此需要我们的 Wdiget 是 StatefulWidget 类型。

现在我们点击能够动态的切换 index,然后我们准备好了 page 的列表,之前我们渲染一个页面都是直接在 body 中配置一个 Widget

比如这种写法:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Card Widget'),
          backgroundColor: Colors.pink,
        ),
        body: HomeContent(),
      ),
    );
  }
}

导航的切换时需要动态的变更 主页 body 的内容,因此需要动态的配置 body 的组件:

body: this._pageList[this._currentIndex],

4、完整的页面

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

  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _currentIndex = 0;

  List<Widget> _pageList = [
    HomePage(),
    NewsPage(),
    MyPage(),
  ];

  List<BottomNavigationBarItem> _barItem = [
    BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
    BottomNavigationBarItem(icon: Icon(Icons.list), title: Text('新闻')),
    BottomNavigationBarItem(icon: Icon(Icons.people), title: Text('我的')),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('BottomNavigationBar'),
          backgroundColor: Colors.pink,
        ),
        body: this._pageList[this._currentIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: (int index) {
            setState(() {
              this._currentIndex = index;
            });
          },
          currentIndex: this._currentIndex,
          items: _barItem,
          fixedColor: Colors.pink,
          selectedFontSize: 12,
          type: BottomNavigationBarType.fixed,
        ),
      ),
    );
  }
}

三、最终效果

1.gif

四、代码

完整代码地址:https://github.com/postbird/FlutterHelloWorldDemo/blob/master/demo1/lib/bak/main.26-BottomNavigationBar%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.dart