喜迎
春节

有状态组件


Flutter有状态组件是UI中可根据时间变化而更新的部分,与无状态组件不同,拥有State对象,用于存储和管理组件的状态。

1 StatefulWidget

  • 在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget或StatefulWidget。
    • StatelessWidget,无状态组件,状态不可变的Widget,前期接触比较多。
    • StatefulWidget,有状态组件,持有的状态可能在Widget生命周期中改变。

1-1 无状态计数器

  • Android Studio使用快捷键stless + Tab,生成StatelessWidget代码块。
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  int countNum = 0;
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('无状态计数器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$countNum', style: Theme.of(context).textTheme.headline1),
            const SizedBox(height: 100),
            ElevatedButton(
              onPressed: () {
                // 增加时值会改变,页面不会变
                countNum++;
                // 在StatelessWidget中无法调用
                // setState(() {countNum++;});
                print(countNum);
              },
              child: const Text('增加'),
            ),
          ],
        ),
      ),
    );
  }
}

1-2 有状态计数器

  • Android Studio使用快捷键stful + Tab,生成StatefulWidget代码块。
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

// 有状态组件
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _numCount = 0;

  @override
  Widget build(BuildContext context) {
    // 打印出来
    print(_numCount);
    return Scaffold(
      appBar: AppBar(title: const Text('有状态计数器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_numCount', style: Theme.of(context).textTheme.headline2),
            const SizedBox(height: 60),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _numCount++;
                });
              },
              child: const Text('增加'),
            ),
          ],
        ),
      ),
    );
  }
}

1-3 实现浮动按钮

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

// 有状态组件
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _numCount = 0;

  @override
  Widget build(BuildContext context) {
    // 打印出来
    print(_numCount);
    return Scaffold(
      appBar: AppBar(title: const Text('实现浮动按钮')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_numCount', style: Theme.of(context).textTheme.headline2),
            const SizedBox(height: 60),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _numCount++;
                });
              },
              child: const Text('增加'),
            ),
          ],
        ),
      ),
      // 浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _numCount++;
          });
        },
        // 按钮样式
        child: const Icon(Icons.add),
      ),
    );
  }
}

1-4 实现动态列表

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // _list是私有属性,必须加final
  final List<String> _list = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('实现动态列表')),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          // 改变数据必须加上setState
          setState(() {
            _list.add('新增列表');
          });
        },
      ),
      body: ListView(
        // children: const [
        //   ListTile(title: Text('列表')),
        //   ListTile(title: Text('列表')),
        //   ListTile(title: Text('列表')),
        // ],
        children: _list.map((v) {
          return ListTile(title: Text(v));
        }).toList(),
      ),
    );
  }
}

2 自定义底部导航

  • BottomNavigationBar,底部导航条,是Scaffold组件的参数。
  • 参数说明
    • items(List底部导航条按钮的集合)、currentIndex(默认选中第几个)。
    • iconSize(icon)、onTap(选中变化回调函数)、fixedColor(选中颜色)。
    • type(BottomNavigationBarType.fixed、BottomNavigationBarType.shifting)。

2-1 实现底部导航

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const Tabs(),
    );
  }
}

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('实现底部导航')),
      bottomNavigationBar: BottomNavigationBar(
        // 默认选中分类(第二个)
        // currentIndex: 1,
        currentIndex: _currentIndex,
        onTap: (index) {
          // 获取点击的索引值
          // print(index);
          // 注意调用setState
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '设置',
          ),
        ],
      ),
    );
  }
}

2-2 实现页面切换

# myflutter(项目名)目录结构如下
- pages/
  - tabs/
    - user.dart
    - home.dart
    - setting.dart
    - category.dart
  - tabs.dart
- main.dart

(1) user.dart

import 'package:flutter/material.dart';

class UserPage extends StatefulWidget {
  const UserPage({Key? key}) : super(key: key);

  @override
  State<UserPage> createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('用户信息'),
    );
  }
}

(2) tabs.dart

import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('实现页面切换')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 40,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        // 默认选中分类(第二个)
        // currentIndex: 1,
        currentIndex: _currentIndex,
        onTap: (index) {
          // 获取点击的索引值
          // print(index);
          // 注意调用setState
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '设置',
          ),
          // 没有type菜单图标会被挤掉
          BottomNavigationBarItem(
            icon: Icon(Icons.people),
            label: '用户',
          ),
        ],
      ),
    );
  }
}

(3) main.dart

import './pages/tabs.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const Tabs(),
    );
  }
}

(4) home.dart

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('首页'),
    );
  }
}

(5) setting.dart

import 'package:flutter/material.dart';

class SettingPage extends StatefulWidget {
  const SettingPage({Key? key}) : super(key: key);

  @override
  State<SettingPage> createState() => _SettingPageState();
}

class _SettingPageState extends State<SettingPage> {
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('系统设置'),
    );
  }
}

(6) category.dart

import 'package:flutter/material.dart';

class CategoryPage extends StatefulWidget {
  const CategoryPage({Key? key}) : super(key: key);

  @override
  State<CategoryPage> createState() => _CategoryPageState();
}

class _CategoryPageState extends State<CategoryPage> {
  @override
  Widget build(BuildContext context) {
    // return const Center(
    //   child: Text('分类'),
    // );
    return ListView(
      children: const [
        ListTile(title: Text('列表1')),
        ListTile(title: Text('列表2')),
        ListTile(title: Text('列表3')),
      ],
    );
  }
}

3 凸起按钮的导航

  • FloatingActionButton简称FAB,可实现浮动按钮,也可实现底部凸起导航,是Scaffold组件的参数。
  • 属性说明
    • elevation(未点击时的阴影)、hignlightElevation(点击时的阴影值,默认为12.0)。
    • onPressed(点击事件回调)、shape(可定义FAB的形状等)、mini(是否为mini类型,默认false)。
    • child(子视图,一般为Icon,不推荐使用文字)、tooltip(FAB被长按时显示,也是无障碍功能)。
# myflutter(项目名)目录结构如下,新增message.dart,除tabs.dart,其他内容不变
- pages/
  - tabs/
    - user.dart
    - home.dart
    - setting.dart
    - message.dart【+】
    - category.dart
  - tabs.dart【🖊】
- main.dart

3-1 tabs.dart

import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('凸起按钮的导航')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        // 默认选中分类(第二个)
        // currentIndex: 1,
        currentIndex: _currentIndex,
        onTap: (index) {
          // 获取点击的索引值
          // print(index);
          // 注意调用setState
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: '消息',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '设置',
          ),
          // 没有type菜单图标会被挤掉
          BottomNavigationBarItem(
            icon: Icon(Icons.people),
            label: '用户',
          ),
        ],
      ),
      floatingActionButton: Container(
        // 调整FloatingActionButton的大小
        height: 60,
        width: 60,
        padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              // 点击凸起+按钮会显示消息页面
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

3-2 message.dart

import 'package:flutter/material.dart';

class MessagePage extends StatefulWidget {
  const MessagePage({Key? key}) : super(key: key);

  @override
  State<MessagePage> createState() => _MessagePageState();
}

class _MessagePageState extends State<MessagePage> {
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('消息'),
    );
  }
}

4 Drawer抽屉视图

  • Scaffold组件中传入drawer参数可定义左侧边栏,传入endDrawer可定义右侧边栏。
  • 侧边栏默认是隐藏的,可通过手指滑动显示侧边栏,也可通过点击按钮显示侧边栏。

4-1 创建抽屉头部

  • DrawerHeader属性:decoration(顶部背景颜色)、child(子元素)、padding(内边距)、margin(外边距)。
// tabs.dart【🖊】,其余内容不变
import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('创建抽屉头部')),
      drawer: Drawer(
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  flex: 1,
                  child: DrawerHeader(
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      image: DecorationImage(
                        image: NetworkImage(
                          'https://img0.baidu.com/it/u=1884790922,'
                          '3388645850&fm=253&fmt=auto&app=138&f=JP'
                          'EG?w=336&h=252',
                        ),
                        fit: BoxFit.cover,
                      ),
                    ),
                    child: Column(
                      children: const [
                        ListTile(
                          leading: CircleAvatar(
                            backgroundImage: NetworkImage(
                              'https://img0.baidu.com/it/u=343412189,'
                              '4190523006&fm=253&fmt=auto&app=138&f=J'
                              'PEG?w=300&h=300',
                            ),
                          ),
                          title: Text(
                            '钱三两',
                            style: TextStyle(color: Colors.blue),
                          ),
                        ),
                        ListTile(title: Text('邮箱:Siren1996@qq.com')),
                      ],
                    ),
                  ),
                ),
              ],
            ),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.people)),
              title: Text('个人中心'),
            ),
            const Divider(),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.settings)),
              title: Text('系统设置'),
            ),
            const Divider(),
          ],
        ),
      ),
      endDrawer: const Drawer(child: Text('右侧侧边栏')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
        ],
      ),
      // 调整FloatingActionButton的大小
      floatingActionButton: Container(
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

4-2 账户抽屉头部

  • UserAccountsDrawerHeader常见属性
    • currentAccountPicture(账户头像)、otherAccountsPictures(用来设置当前账户的其他账户头像)。
    • decoration(顶部背景颜色)、accountName(账户名)、accountEmail(账户邮箱)、margin(外边距)。
// tabs.dart【🖊】,其余内容不变
import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('账户抽屉头部')),
      drawer: Drawer(
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  flex: 1,
                  child: UserAccountsDrawerHeader(
                    accountName: const Text('钱三两'),
                    accountEmail: const Text('Siren1996@qq.com'),
                    otherAccountsPictures: [
                      Image.network(
                        'https://wx2.sinaimg.cn/mw690/714'
                        'ded47ly1g1xq9wc5uuj21e01e0jur.jpg',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage(
                        'https://img0.baidu.com/it/u=343412189,'
                        '4190523006&fm=253&fmt=auto&app=138&f=J'
                        'PEG?w=300&h=300',
                      ),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(
                          'https://img0.baidu.com/it/u=1884790922,'
                          '3388645850&fm=253&fmt=auto&app=138&f=JP'
                          'EG?w=336&h=252',
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.people)),
              title: Text('个人中心'),
            ),
            const Divider(),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.settings)),
              title: Text('系统设置'),
            ),
            const Divider(),
          ],
        ),
      ),
      endDrawer: const Drawer(child: Text('右侧侧边栏')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
        ],
      ),
      // 调整FloatingActionButton的大小
      floatingActionButton: Container(
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

5 自定义顶部按钮

  • AppBar自定义顶部按钮图标、颜色,属性如下。
    • actions(通常使用IconButton来表示,可以放按钮组件)、iconTheme(图标样式)。
    • bottom(通常放tabBar,标题下面显示一个Tab导航栏)、backgroundColor(导航的背景颜色)。
    • title(标题,通常显示为当前界面的标题文字,可放组件)、centerTitle(标题是否居中显示)。
    • leading(在标题前面显示的一个控件,通常为首页显示应用的logo,或其他界面的返回按钮)。

5-1 实现顶部按钮

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 去掉右上角dubug图标
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () {
            print('左侧按钮图标');
          },
        ),
        // 导航的背景颜色
        backgroundColor: Colors.red,
        title: const Text('实现顶部按钮'),
        // 右侧的按钮图标
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {
              print('搜索');
            },
          ),
          IconButton(
            icon: const Icon(Icons.more_horiz),
            onPressed: () {
              print('更多');
            },
          ),
        ],
      ),
    );
  }
}

5-2 顶部滑动导航

  • TabBar常见属性,indicatorSize:指示器大小计算方式如下(两个等宽)。
  • TabBarIndicatorSize.label(跟文字等宽)、TabBarIndicatorSize.tab(跟每个tab等宽)。
  • unselectedLabelColor(未选中label颜色)、unselectedLabelStyle(未选中label的Style)。
  • indicatorPadding(底部指示器的Padding)、indicator(指示器decoration,例如边框等)。
  • tabs(显示的标签内容,一般使用Tab对象,也可以是其他组件)、isScrollable(是否可以滚动)。
  • controller(TabController对象)、indicatorColor(指示器颜色)、indicatorWeight(指示器高度)。
  • labelColor(选中label颜色)、labelStyle(选中label样式)、labelPadding(每个label的padding值)。
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 去掉右上角dubug图标
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  // 生命周期函数(组件初始化时触发)
  @override
  void initState() {
    super.initState();
    // length(导航标题)够多可左右滑动
    _tabController = TabController(length: 5, vsync: this);
  }

  List<String> list = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // 左侧的按钮图标
        leading: IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () {
            print('左侧按钮图标');
          },
        ),
        // 导航的背景颜色
        backgroundColor: Colors.red,
        title: const Text('顶部滑动导航'),
        // 右侧的按钮图标
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {
              print('搜索');
            },
          ),
          IconButton(
            icon: const Icon(Icons.more_horiz),
            onPressed: () {
              print('更多');
            },
          ),
        ],
        bottom: TabBar(
          // 设置可以左右滚动
          isScrollable: true,
          // 指示器颜色
          indicatorColor: Colors.white,
          // 指示器高度
          indicatorWeight: 2,
          // 底部指示器的Padding
          indicatorPadding: const EdgeInsets.all(5),
          // 与文字等宽,默认tab
          // indicatorSize: TabBarIndicatorSize.label,
          // 选中的label颜色
          labelColor: Colors.yellow,
          // 未选中的label颜色
          unselectedLabelColor: Colors.white,
          // 选中的label样式
          labelStyle: const TextStyle(fontSize: 15),
          unselectedLabelStyle: const TextStyle(fontSize: 12),
          // 指示器边框
          indicator: BoxDecoration(
            color: Colors.blue,
            // 边框角幅度
            borderRadius: BorderRadius.circular(10),
          ),
          // 配置controller需要去掉const
          controller: _tabController,
          tabs: const [
            Tab(child: Text('关注')),
            Tab(child: Text('热门')),
            Tab(child: Text('视频')),
            Tab(child: Text('图片')),
            Tab(child: Text('推荐')),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          // const Text('我是关注'),
          ListView(children: const [ListTile(title: Text('动态关注'))]),
          ListView(children: const [ListTile(title: Text('热门信息'))]),
          ListView(children: const [ListTile(title: Text('视频加载'))]),
          ListView(children: const [ListTile(title: Text('图片赏析'))]),
          ListView(children: const [ListTile(title: Text('推荐列表'))]),
        ],
      ),
    );
  }
}

6 底部与顶部结合

  • PreferredSize可以改变AppBar高度,AutomaticKeepAliveClientMixin可以快速实现页面缓存功能。
  • 但是通过混入的方式实现不是很优雅,所以有必要对AutomaticKeepAliveClientMixin混入进行封装。

6-1 底部页面使用导航

# myflutter(项目名)目录结构如下,更改main.dart、tabs.dart、home.dart内容,其他不变
- pages/
  - tabs/
    - user.dart
    - home.dart【🖊】
    - setting.dart
    - message.dart
    - category.dart
  - tabs.dart【🖊】
- main.dart【🖊】

(1) tabs.dart

import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 1,
        backgroundColor: Colors.red,
        title: const Text('底部页面使用导航'),
      ),
      drawer: Drawer(
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  flex: 1,
                  child: UserAccountsDrawerHeader(
                    accountName: const Text('钱三两'),
                    accountEmail: const Text('Siren1996@qq.com'),
                    otherAccountsPictures: [
                      Image.network(
                        'https://wx2.sinaimg.cn/mw690/714'
                        'ded47ly1g1xq9wc5uuj21e01e0jur.jpg',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage(
                        'https://img0.baidu.com/it/u=343412189,'
                        '4190523006&fm=253&fmt=auto&app=138&f=J'
                        'PEG?w=300&h=300',
                      ),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(
                          'https://img0.baidu.com/it/u=1884790922,'
                          '3388645850&fm=253&fmt=auto&app=138&f=JP'
                          'EG?w=336&h=252',
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.people)),
              title: Text('个人中心'),
            ),
            const Divider(),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.settings)),
              title: Text('系统设置'),
            ),
            const Divider(),
          ],
        ),
      ),
      // endDrawer: const Drawer(child: Text('右侧侧边栏')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
        ],
      ),
      // 调整FloatingActionButton的大小
      floatingActionButton: Container(
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

(2) main.dart

import './pages/tabs.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 去掉右上角的debug标识
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const Tabs(),
    );
  }
}

(3) home.dart

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 8, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 可以配置appBar的高度
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(40),
        child: AppBar(
          elevation: 0.5,
          backgroundColor: Colors.white,
          title: SizedBox(
            // 修改TabBar的高度
            height: 30,
            child: TabBar(
              labelStyle: const TextStyle(fontSize: 15),
              isScrollable: true,
              // 底部指示器的颜色
              indicatorColor: Colors.red,
              labelColor: Colors.red,
              // label未选中的颜色
              unselectedLabelColor: Colors.black,
              indicatorSize: TabBarIndicatorSize.label,
              controller: _tabController,
              tabs: const [
                Tab(child: Text('关注')),
                Tab(child: Text('热门')),
                Tab(child: Text('生活')),
                Tab(child: Text('娱乐')),
                Tab(child: Text('绘画')),
                Tab(child: Text('设计')),
                Tab(child: Text('编程')),
                Tab(child: Text('职场')),
              ],
            ),
          ),
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          ListView(children: const [
            ListTile(title: Text('关注列表01')),
            ListTile(title: Text('关注列表02')),
            ListTile(title: Text('关注列表03')),
            ListTile(title: Text('关注列表04')),
            ListTile(title: Text('关注列表05')),
            ListTile(title: Text('关注列表06')),
            ListTile(title: Text('关注列表07')),
            ListTile(title: Text('关注列表08')),
            ListTile(title: Text('关注列表09')),
            ListTile(title: Text('关注列表10')),
            ListTile(title: Text('关注列表11')),
            ListTile(title: Text('关注列表12')),
            ListTile(title: Text('关注列表13')),
            ListTile(title: Text('关注列表14')),
            ListTile(title: Text('关注列表15')),
            ListTile(title: Text('关注列表16')),
            ListTile(title: Text('关注列表17')),
            ListTile(title: Text('关注列表18')),
            ListTile(title: Text('关注列表19')),
            ListTile(title: Text('关注列表20')),
          ]),
          const Text('热门'),
          const Text('生活'),
          const Text('娱乐'),
          const Text('绘画'),
          const Text('设计'),
          const Text('编程'),
          const Text('职场'),
        ],
      ),
    );
  }
}

6-2 自定义的缓存组件

# myflutter(项目名)目录结构如下,新增KeepAliveWrapper.dart
# 更改tabs.dart、home.dart内容,其他不变
- pages/
  - tabs/
    - user.dart
    - home.dart【🖊】
    - setting.dart
    - message.dart
    - category.dart
  - tabs.dart【🖊】
- tools/
  - KeepAliveWrapper.dart【+】
- main.dart

(1) tabs.dart

import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 1,
        backgroundColor: Colors.red,
        title: const Text('自定义的缓存组件'),
      ),
      drawer: Drawer(
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  flex: 1,
                  child: UserAccountsDrawerHeader(
                    accountName: const Text('钱三两'),
                    accountEmail: const Text('Siren1996@qq.com'),
                    otherAccountsPictures: [
                      Image.network(
                        'https://wx2.sinaimg.cn/mw690/714'
                        'ded47ly1g1xq9wc5uuj21e01e0jur.jpg',
                      ),
                      Image.network(
                        'https://img2.baidu.com/it/u=3251345887,'
                        '2581092454&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage(
                        'https://img0.baidu.com/it/u=343412189,'
                        '4190523006&fm=253&fmt=auto&app=138&f=J'
                        'PEG?w=300&h=300',
                      ),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(
                          'https://img0.baidu.com/it/u=1884790922,'
                          '3388645850&fm=253&fmt=auto&app=138&f=JP'
                          'EG?w=336&h=252',
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.people)),
              title: Text('个人中心'),
            ),
            const Divider(),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.settings)),
              title: Text('系统设置'),
            ),
            const Divider(),
          ],
        ),
      ),
      // endDrawer: const Drawer(child: Text('右侧侧边栏')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
        ],
      ),
      // 调整FloatingActionButton的大小
      floatingActionButton: Container(
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

(2) home.dart

import 'package:flutter/material.dart';
import '../../tools/KeepAliveWrapper.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 8, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 可以配置appBar的高度
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(40),
        child: AppBar(
          elevation: 0.5,
          backgroundColor: Colors.white,
          title: SizedBox(
            // 修改TabBar的高度
            height: 30,
            child: TabBar(
              labelStyle: const TextStyle(fontSize: 15),
              isScrollable: true,
              // 底部指示器的颜色
              indicatorColor: Colors.red,
              labelColor: Colors.red,
              // label未选中的颜色
              unselectedLabelColor: Colors.black,
              indicatorSize: TabBarIndicatorSize.label,
              controller: _tabController,
              tabs: const [
                Tab(child: Text('关注')),
                Tab(child: Text('热门')),
                Tab(child: Text('生活')),
                Tab(child: Text('娱乐')),
                Tab(child: Text('绘画')),
                Tab(child: Text('设计')),
                Tab(child: Text('编程')),
                Tab(child: Text('职场')),
              ],
            ),
          ),
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          // KeepAliveWrapper自定义缓存组件
          KeepAliveWrapper(
            child: ListView(
              children: const [
                ListTile(title: Text('关注列表01')),
                ListTile(title: Text('关注列表02')),
                ListTile(title: Text('关注列表03')),
                ListTile(title: Text('关注列表04')),
                ListTile(title: Text('关注列表05')),
                ListTile(title: Text('关注列表06')),
                ListTile(title: Text('关注列表07')),
                ListTile(title: Text('关注列表08')),
                ListTile(title: Text('关注列表09')),
                ListTile(title: Text('关注列表10')),
                ListTile(title: Text('关注列表11')),
                ListTile(title: Text('关注列表12')),
                ListTile(title: Text('关注列表13')),
                ListTile(title: Text('关注列表14')),
                ListTile(title: Text('关注列表15')),
                ListTile(title: Text('关注列表16')),
                ListTile(title: Text('关注列表17')),
                ListTile(title: Text('关注列表18')),
                ListTile(title: Text('关注列表19')),
                ListTile(title: Text('关注列表20')),
              ],
            ),
          ),
          const Text('热门'), const Text('生活'), const Text('娱乐'),
          const Text('绘画'),
          const Text('设计'), const Text('编程'), const Text('职场'),
        ],
      ),
    );
  }
}

(3) KeepAliveWrapper.dart

import 'package:flutter/material.dart';

class KeepAliveWrapper extends StatefulWidget {
  const KeepAliveWrapper(
      {Key? key, @required this.child, this.keepAlive = true})
      : super(key: key);

  final Widget? child;
  final bool keepAlive;

  @override
  State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    return widget.child!;
  }

  @override
  bool get wantKeepAlive => widget.keepAlive;

  @override
  void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
    if (oldWidget.keepAlive != widget.keepAlive) {
      // keepAlive状态需要更新,实现在AutomaticKeepAliveClientMixin中
      updateKeepAlive();
    }
    super.didUpdateWidget(oldWidget);
  }
}

6-3 获取选项卡索引值

# myflutter(项目名)目录结构如下,更改tabs.dart、home.dart内容,其他不变
- pages/
  - tabs/
    - user.dart
    - home.dart【🖊】
    - setting.dart
    - message.dart
    - category.dart
  - tabs.dart【🖊】
- tools/
  - KeepAliveWrapper.dart
- main.dart

(1) tabs.dart

import './tabs/user.dart';
import './tabs/home.dart';
import './tabs/setting.dart';
import './tabs/message.dart';
import './tabs/category.dart';
import 'package:flutter/material.dart';

class Tabs extends StatefulWidget {
  const Tabs({Key? key}) : super(key: key);

  @override
  State<Tabs> createState() => _TabsState();
}

class _TabsState extends State<Tabs> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    MessagePage(),
    SettingPage(),
    UserPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 1,
        backgroundColor: Colors.red,
        title: const Text('获取选项卡索引值'),
      ),
      drawer: Drawer(
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  flex: 1,
                  child: UserAccountsDrawerHeader(
                    accountName: const Text('钱三两'),
                    accountEmail: const Text('Siren1996@qq.com'),
                    otherAccountsPictures: [
                      Image.network(
                        'https://wx2.sinaimg.cn/mw690/714'
                        'ded47ly1g1xq9wc5uuj21e01e0jur.jpg',
                      ),
                      Image.network(
                        'https://img2.baidu.com/it/u=3251345887,'
                        '2581092454&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                      Image.network(
                        'https://img1.baidu.com/it/u=2293783843,'
                        '2984695986&fm=253&fmt=auto&app=120&f=JP'
                        'EG?w=500&h=500',
                      ),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage(
                        'https://img0.baidu.com/it/u=343412189,'
                        '4190523006&fm=253&fmt=auto&app=138&f=J'
                        'PEG?w=300&h=300',
                      ),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(
                          'https://img0.baidu.com/it/u=1884790922,'
                          '3388645850&fm=253&fmt=auto&app=138&f=JP'
                          'EG?w=336&h=252',
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.people)),
              title: Text('个人中心'),
            ),
            const Divider(),
            const ListTile(
              leading: CircleAvatar(child: Icon(Icons.settings)),
              title: Text('系统设置'),
            ),
            const Divider(),
          ],
        ),
      ),
      // endDrawer: const Drawer(child: Text('右侧侧边栏')),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        // 选中的颜色
        fixedColor: Colors.red,
        // 配置底部菜单大小
        iconSize: 25,
        // 4个以上菜单需配置此项
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
        ],
      ),
      // 调整FloatingActionButton的大小
      floatingActionButton: Container(
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        // 调整FloatingActionButton的位置
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(30),
        ),
        child: FloatingActionButton(
          backgroundColor: _currentIndex == 2 ? Colors.red : Colors.blue,
          child: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              _currentIndex = 2;
            });
          },
        ),
      ),
      // 配置浮动按钮的位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

(2) home.dart

import 'package:flutter/material.dart';
import '../../tools/KeepAliveWrapper.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 8, vsync: this);
    // 监听_tabController的改变事件
    _tabController.addListener(() {
      // 获取两次
      // print(_tabController.index);
      if (_tabController.animation!.value == _tabController.index) {
        // 获取索引值方法①:获取点击或滑动页面的索引值
        print(_tabController.index);
      }
    });
  }

  // 组件销毁时触发
  @override
  // 例如点击底部导航的分类
  void dispose() {
    super.dispose();
    // 销毁_tabController
    _tabController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 可以配置appBar的高度
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(40),
        child: AppBar(
          elevation: 0.5,
          backgroundColor: Colors.white,
          title: SizedBox(
            // 修改TabBar的高度
            height: 30,
            child: TabBar(
              labelStyle: const TextStyle(fontSize: 15),
              isScrollable: true,
              // 底部指示器的颜色
              indicatorColor: Colors.red,
              labelColor: Colors.red,
              // label未选中的颜色
              unselectedLabelColor: Colors.black,
              indicatorSize: TabBarIndicatorSize.label,
              controller: _tabController,
              // 获取索引值方法②:只能监听点击事件,没法监听滑动
              // onTap: (index) {
              //   print(index);
              // },
              tabs: const [
                Tab(child: Text('关注')),
                Tab(child: Text('热门')),
                Tab(child: Text('生活')),
                Tab(child: Text('娱乐')),
                Tab(child: Text('绘画')),
                Tab(child: Text('设计')),
                Tab(child: Text('编程')),
                Tab(child: Text('职场')),
              ],
            ),
          ),
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          // KeepAliveWrapper自定义缓存组件
          KeepAliveWrapper(
            child: ListView(
              children: const [
                ListTile(title: Text('关注列表01')),
                ListTile(title: Text('关注列表02')),
                ListTile(title: Text('关注列表03')),
                ListTile(title: Text('关注列表04')),
                ListTile(title: Text('关注列表05')),
                ListTile(title: Text('关注列表06')),
                ListTile(title: Text('关注列表07')),
                ListTile(title: Text('关注列表08')),
                ListTile(title: Text('关注列表09')),
                ListTile(title: Text('关注列表10')),
                ListTile(title: Text('关注列表11')),
                ListTile(title: Text('关注列表12')),
                ListTile(title: Text('关注列表13')),
                ListTile(title: Text('关注列表14')),
                ListTile(title: Text('关注列表15')),
                ListTile(title: Text('关注列表16')),
                ListTile(title: Text('关注列表17')),
                ListTile(title: Text('关注列表18')),
                ListTile(title: Text('关注列表19')),
                ListTile(title: Text('关注列表20')),
              ],
            ),
          ),
          const Text('热门'), const Text('生活'), const Text('娱乐'),
          const Text('绘画'),
          const Text('设计'), const Text('编程'), const Text('职场'),
        ],
      ),
    );
  }
}

文章作者: bsf
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 bsf !
评 论
 上一篇
路由详解
路由详解
Flutter中的路由即页面跳转,通过Navigator组件管理路由导航并提供管理堆栈的方法,例如Navigator.push和Navigator.pop。
2024-05-16
下一篇 
页面布局
页面布局
Flutter页面布局主要使用Row和Column两种基本布局组件,可组合使用并结合Expanded、Flex等组件实现更灵活的页面布局。
2024-04-11
  目录