有状态组件(一)


Flutter有状态组件是指可以在生命周期内维护状态的组件,可以通过数据的变化来改变自身的外观和行为。

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);

  
  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);

  
  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++;                             // 增加时值会改变,页面不会变
                // setState(() {countNum++;});          // 在StatelessWidget中无法调用
                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);

  
  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);

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

class _HomePageState extends State<HomePage> {
  int _numCount = 0;
  
  
  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);

  
  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);

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

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

  
  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);

  
  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);

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

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

  
  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);

  
  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);

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

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

  
  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) 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);

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

(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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("实现页面切换")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(3) user.dart

import 'package:flutter/material.dart';

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

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

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

(4) home.dart

import 'package:flutter/material.dart';

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

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

class _HomePageState extends State<HomePage> {
  
  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);

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

class _SettingPageState extends State<SettingPage> {
  
  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);

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

class _CategoryPageState extends State<CategoryPage> {
  
  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(项目名)目录结构如下,除tabs.dart外,其余内容不变,多了message.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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("凸起按钮的导航")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 25,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
      floatingActionButton: Container(
        height: 60,                                     // 调整FloatingActionButton的大小
        width: 60,
        padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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);

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

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

4 Drawer抽屉视图

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

4-1 创建抽屉头部

  • DrawerHeader常见属性:decoration(顶部背景颜色)、child(子元素)、padding(内边距)、margin(外边距)。
import './tabs/user.dart';                              // tabs.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);

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

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

  
  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.yellow,
                      image: DecorationImage(
                        image: NetworkImage("http://www.m58.link/QKBLQ"),
                        fit: BoxFit.cover,
                      ),
                    ),
                    child: Column(
                      children: const [
                        ListTile(
                          leading: CircleAvatar(
                            backgroundImage: NetworkImage("http://www.m58.link/ymHZl"),
                          ),
                          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,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        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: Container(                  // 调整FloatingActionButton的大小
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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(外边距)。
import './tabs/user.dart';                              // tabs.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);

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

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

  
  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("http://www.m58.link/uPqPD"),
                      Image.network("http://www.m58.link/uFOCk"),
                      Image.network("http://www.m58.link/fKFLL"),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage("http://www.m58.link/ymHZl"),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage("http://www.m58.link/DClbg"),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            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,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        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: Container(                  // 调整FloatingActionButton的大小
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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);

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

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

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

class _HomePageState extends State<HomePage> {
  
  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);

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

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

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

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

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

  
  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,                           // 指示器高度
          indicatorPadding: const EdgeInsets.all(5),    // 底部指示器的Padding
          // indicatorSize: TabBarIndicatorSize.label,  // 与文字等宽,默认tab
          labelColor: Colors.yellow,                    // 选中的label颜色
          unselectedLabelColor: Colors.white,           // 未选中的label颜色
          labelStyle: const TextStyle(fontSize: 15),    // 选中的label样式
          unselectedLabelStyle: const TextStyle(fontSize: 12),
          indicator: BoxDecoration(                     // 指示器边框
            color: Colors.blue,
            borderRadius: BorderRadius.circular(10),    // 边框角幅度
          ),
          controller: _tabController,                   // 配置controller需要去掉const
          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) 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);

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

(2) 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);

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

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

  
  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("http://www.m58.link/uPqPD"),
                      Image.network("http://www.m58.link/uFOCk"),
                      Image.network("http://www.m58.link/fKFLL"),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage("http://www.m58.link/ymHZl"),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage("http://www.m58.link/DClbg"),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            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,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        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: Container(                  // 调整FloatingActionButton的大小
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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) home.dart

import 'package:flutter/material.dart';

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

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(                            // 可以配置appBar的高度
        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,
              unselectedLabelColor: Colors.black,       // label未选中的颜色
              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(项目名)目录结构如下,只更改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);

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

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

  
  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("http://www.m58.link/uPqPD"),
                      Image.network("http://www.m58.link/uFOCk"),
                      Image.network("http://www.m58.link/fKFLL"),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage("http://www.m58.link/ymHZl"),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage("http://www.m58.link/DClbg"),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            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,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        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: Container(                  // 调整FloatingActionButton的大小
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(                            // 可以配置appBar的高度
        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,
              unselectedLabelColor: Colors.black,       // label未选中的颜色
              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,  this.child, this.keepAlive = true}
  ) : super(key: key);

  final Widget? child;
  final bool keepAlive;

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

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

  
  bool get wantKeepAlive => widget.keepAlive;

  
  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);

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

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

  
  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("http://www.m58.link/uPqPD"),
                      Image.network("http://www.m58.link/uFOCk"),
                      Image.network("http://www.m58.link/fKFLL"),
                    ],
                    currentAccountPicture: const CircleAvatar(
                      backgroundImage: NetworkImage("http://www.m58.link/ymHZl"),
                    ),
                    decoration: const BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage("http://www.m58.link/DClbg"),
                      ),
                    ),
                  ),
                ),
              ],
            ),
            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,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        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: Container(                  // 调整FloatingActionButton的大小
        height: 60, width: 60, padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.only(top: 5),          // 调整FloatingActionButton的位置
        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);

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

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(                            // 可以配置appBar的高度
        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,
              unselectedLabelColor: Colors.black,       // label未选中的颜色
              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("职场"),
        ],
      ),
    );
  }
}

7 Flutter路由详解

  • Flutter中的路由即页面跳转,通过Navigator组件管理路由导航,并提供了管理堆栈的方法。
  • 例如Navigator.push和Navigator.pop,提供了基本路由和命名路由两种配置路由跳转的方式。

7-1 普通路由基本使用

# 例如:myflutter(项目名)目录结构如下,修改“自定义底部导航”的“实现页面切换”代码
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart【✔】
#     - setting.dart
#     - category.dart【✔】
#   - tabs.dart【✔】
#   - form.dart【✔】
#   - search.dart【✔】
# - main.dart

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("普通路由基本使用")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) form.dart

import 'package:flutter/material.dart';

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

  
  State<FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("表单"),
      ),
      body: const Center(
        child: Text("表单页面"),
      ),
    );
  }
}

(3) home.dart

import '../form.dart';
import '../search.dart';
import 'package:flutter/material.dart';

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

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

class _HomePageState extends State<HomePage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const SearchPage();              // 首页跳转搜索页面
              })
            );
          }, child: const Text("搜索")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const FormPage();                // 首页跳转表单页面
              })
            );
          }, child: const Text("跳转到表单页面")),
        ],
      ),
    );
  }
}

(4) search.dart

import 'package:flutter/material.dart';

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

  
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("搜索"),
      ),
      body: const Center(
        child: Text("搜索页面"),
      ),
    );
  }
}

(5) category.dart

import '../search.dart';
import 'package:flutter/material.dart';

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

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

class _CategoryPageState extends State<CategoryPage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const SearchPage();              // 分类跳转搜索页面
              })
            );
          }, child: const Text("搜索")),
        ],
      ),
    );
  }
}

7-2 普通路由跳转传值

# 例如:myflutter(项目名)目录结构如下,修改“普通路由基本使用”代码
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart【✔】
#     - setting.dart
#     - category.dart
#   - tabs.dart【✔】
#   - news.dart【✔】
#   - form.dart
#   - search.dart
# - main.dart

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("普通路由跳转传值")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) news.dart

import 'package:flutter/material.dart';

class NewsPage extends StatefulWidget {                 // 新闻页面接收上个页面传来的参数
  final String title; final int aid;
  const NewsPage({Key? key, this.title="新闻", required this.aid}) : super(key: key);

  
  State<NewsPage> createState() => _NewsPageState();
}

class _NewsPageState extends State<NewsPage> {
  
  void initState() {
    super.initState(); print(widget.aid); print(widget.title);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {Navigator.pop(context);},        // 返回到上一页
        child: const Icon(Icons.home),
      ),
      appBar: AppBar(title: Text(widget.title)),        // 获取NewsPage里面定义的title
      body: const Center(child: Text("新闻页面")),
    );
  }
}

(3) home.dart

import '../news.dart';
import '../form.dart';
import '../search.dart';
import 'package:flutter/material.dart';

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

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

class _HomePageState extends State<HomePage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const SearchPage();              // 首页跳转搜索页面
              })
            );
          }, child: const Text("搜索")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const FormPage();                // 首页跳转表单页面
              })
            );
          }, child: const Text("跳转到表单页面")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const NewsPage(title: "我是标题", aid: 12);
              })
            );
          }, child: const Text("跳转传值")),
        ],
      ),
    );
  }
}

7-3 命名路由基本使用

# 例如:myflutter(项目名)目录结构如下,修改“普通路由基本使用”代码,并添加news.dart文件
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart【✔】
#     - setting.dart
#     - category.dart【✔】
#   - tabs.dart【✔】
#   - news.dart【✔】
#   - form.dart
#   - search.dart
# - main.dart【✔】

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("命名路由基本使用")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      // home: const Tabs(),
      initialRoute: "/",                                // 初始化路由
      routes: {                                         // 配置路由
        "/": (context) => const Tabs(),
        "/news": (context) => const NewsPage(),
        "/form": (context) => const FormPage(),
        "/search": (context) {
          return const SearchPage();
        },
      },
    );
  }
}

(3) news.dart

import 'package:flutter/material.dart';

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

  
  State<NewsPage> createState() => _NewsPageState();
}

class _NewsPageState extends State<NewsPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("新闻"),
      ),
      body: const Center(
        child: Text("新闻页面"),
      ),
    );
  }
}

(4) home.dart

import '../search.dart';
import 'package:flutter/material.dart';

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

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

class _HomePageState extends State<HomePage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const SearchPage();              // 首页跳转搜索页面
              })
            );
          }, child: const Text("基本路由跳转,引入组件search.dart")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.pushNamed(context, "/news");
          }, child: const Text("命令路由跳转,main.dart中配置路由")),
        ],
      ),
    );
  }
}

(5) category.dart

import 'package:flutter/material.dart';

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

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

class _CategoryPageState extends State<CategoryPage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.pushNamed(context, "/search");
          }, child: const Text("命名路由跳转到search")),
        ],
      ),
    );
  }
}

7-4 命名路由传值操作

  • 命名路由传值需将路由配置移到外面,首先定义Map类型的routes,调用onGenerateRoute处理。
  • 然后在对应的页面进行接收传值,在页面中定义arguments用于传值,最后再跳转页面实现传参。
# 例如:myflutter(项目名)目录结构如下,修改“命名路由基本使用”代码,并添加shop.dart文件
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart【✔】
#     - setting.dart
#     - category.dart
#   - tabs.dart【✔】
#   - news.dart
#   - form.dart【✔】
#   - shop.dart【✔】
#   - search.dart
# - main.dart【✔】

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("命名路由传值操作")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) shop.dart

import 'package:flutter/material.dart';

class ShopPage extends StatefulWidget {
  final Map arguments;                                  // 接收命名路由跳转传值
  const ShopPage({Key? key, required this.arguments}) : super(key: key);

  
  State<ShopPage> createState() => _ShopPageState();
}

class _ShopPageState extends State<ShopPage> {
  
  void initState() {
    super.initState();
    print(widget.arguments);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("商店"),
      ),
      body: const Center(
        child: Text("商店页面"),
      ),
    );
  }
}

(3) form.dart

import 'package:flutter/material.dart';

class FormPage extends StatefulWidget {                 // 其他页面跳转到form页面
  final Map arguments;                                  // 进行命名路由传值
  const FormPage({Key? key, required this.arguments}) : super(key: key);

  
  State<FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  
  void initState() {
    super.initState();
    print(widget.arguments);                            // 打印输出
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("表单"),
      ),
      body: const Center(
        child: Text("表单页面"),
      ),
    );
  }
}

(4) main.dart

import './pages/tabs.dart';
import './pages/news.dart';
import './pages/form.dart';
import './pages/shop.dart';
import './pages/search.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  Map routes = {                                        // 定义一个Map类型的路由
    "/": (context) => const Tabs(),
    "/news": (context) => const NewsPage(),
    "/form": (context, {arguments}) => FormPage(arguments: arguments),
    "/shop": (context, {arguments}) {
      return ShopPage(arguments: arguments);
    },
    "/search": (context) {
      return const SearchPage();
    },
  };

  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      // home: const Tabs(),
      initialRoute: "/",                                // 初始化路由
      onGenerateRoute: (RouteSettings settings) {       // 固定写法,配置onGenerateRoute
        print(settings);
        print(settings.name);                           // “/news”或“/search”
        print(settings.arguments);

        final String? name = settings.name;             // 统一处理
        final Function? pageContentBuilder = routes[name];
        if (pageContentBuilder != null) {
          if (settings.arguments != null) {
            final Route route = MaterialPageRoute(
              builder: (context) => pageContentBuilder(
                context, arguments: settings.arguments
              )
            );
            return route;
          } else {
            final Route route = MaterialPageRoute(
              builder: (context) => pageContentBuilder(context)
            );
            return route;
          }
        }
        return null;
      },
    );
  }
}

(5) home.dart

import '../search.dart';
import 'package:flutter/material.dart';

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

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

class _HomePageState extends State<HomePage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: (){
            Navigator.of(context).push(                 // 跳转路由
              MaterialPageRoute(builder: (BuildContext context) {
                return const SearchPage();              // 首页跳转搜索页面
              })
            );
          }, child: const Text("基本路由跳转,引入组件search.dart")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.pushNamed(context, "/news");
          }, child: const Text("命令路由跳转,main.dart中配置路由")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.pushNamed(context, "/form", arguments: {
              "title": "命名路由传值",
              "aid": 20
            });
          }, child: const Text("命令路由传值操作,跳转到form页面")),
          const SizedBox(height: 20),
          ElevatedButton(onPressed: (){
            Navigator.pushNamed(context, "/shop", arguments: {
              "title": "命名路由传值",
              "aid": 30
            });
          }, child: const Text("命令路由传值操作,跳转到shop页面")),
        ],
      ),
    );
  }
}

7-5 命名路由代码抽离

# 例如:myflutter(项目名)目录结构如下,修改“命名路由传值操作”代码,并添加routes.dart文件
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart
#     - setting.dart
#     - category.dart
#   - tabs.dart【✔】
#   - news.dart
#   - form.dart
#   - shop.dart
#   - search.dart
# - routes
#   - routes.dart【✔】
# - main.dart【✔】

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("命名路由代码抽离")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      // home: const Tabs(),
      initialRoute: "/",                                // 初始化路由
      onGenerateRoute: onGenerateRoute,                 // 固定写法,配置onGenerateRoute
    );
  }
}

(3) routes.dart

import '../pages/tabs.dart';
import '../pages/news.dart';
import '../pages/form.dart';
import '../pages/shop.dart';
import '../pages/search.dart';
import 'package:flutter/material.dart';

Map routes = {                                          // 定义一个Map类型的路由
  "/": (context) => const Tabs(),
  "/news": (context) => const NewsPage(),
  "/form": (context, {arguments}) => FormPage(arguments: arguments),
  "/shop": (context, {arguments}) {
    return ShopPage(arguments: arguments);
  },
  "/search": (context) {
    return const SearchPage();
  },
};

// 配置onGenerateRoute,固定写法,该方法相当于一个中间件,可以做权限判断
// ignore: prefer_function_declarations_over_variables
var onGenerateRoute = (RouteSettings settings) {
  print(settings);
  print(settings.name);                                 // “/news”或“/search”
  print(settings.arguments);

  final String? name = settings.name;                   // 统一处理
  final Function? pageContentBuilder = routes[name];
  if (pageContentBuilder != null) {
    if (settings.arguments != null) {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(
          context, arguments: settings.arguments
        )
      );
      return route;
    } else {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(context)
      );
      return route;
    }
  }
  return null;
};

7-6 返回到上一级路由

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

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("返回到上一级路由")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) user.dart

import 'package:flutter/material.dart';

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

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

class _UserPageState extends State<UserPage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: () {
            print("执行跳转");
            Navigator.pushNamed(context, "/login");
          }, child: const Text("登录")),
          const SizedBox(height: 40),
          ElevatedButton(onPressed: () {
          }, child: const Text("注册")),
        ],
      ),
    );
  }
}

(3) main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      // home: const Tabs(),
      initialRoute: "/",                                // 初始化路由
      onGenerateRoute: onGenerateRoute,                 // 固定写法,配置onGenerateRoute
    );
  }
}

(4) login.dart

import 'package:flutter/material.dart';

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

  
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("登录页面")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("登录跳转演示,执行登录后返回到上一个页面"),
            const SizedBox(height: 40),
            ElevatedButton(onPressed: () {
              Navigator.of(context).pop();              // 返回到上一级页面
            }, child: const Text("执行登录")),
          ],
        ),
      ),
    );
  }
}

(5) home.dart

import 'package:flutter/material.dart';

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

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

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

(6) routes.dart

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

Map routes = {                                          // 定义一个Map类型的路由
  "/": (context) => const Tabs(),
  "/login": (context) => const LoginPage(),
};

// 配置onGenerateRoute,固定写法,该方法相当于一个中间件,可以做权限判断
// ignore: prefer_function_declarations_over_variables
var onGenerateRoute = (RouteSettings settings) {
  final String? name = settings.name;                   // 统一处理
  final Function? pageContentBuilder = routes[name];
  if (pageContentBuilder != null) {
    if (settings.arguments != null) {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(
          context, arguments: settings.arguments
        )
      );
      return route;
    } else {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(context)
      );
      return route;
    }
  }
  return null;
};

(7) setting.dart

import 'package:flutter/material.dart';

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

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

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

(8) category.dart

import 'package:flutter/material.dart';

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

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

class _CategoryPageState extends State<CategoryPage> {
  
  Widget build(BuildContext context) {
    return const Center(
      child: Text("分类页面"),
    );
  }
}

7-7 Flutter中替换路由

  • 比如:registerFirst > registerSecond > registerThird > registerFirst。
  • registerSecond页,通过pushReplacementNamed()跳转到registerThird页。
  • Navigator.of(context).pushReplacementNamed('/registerSecond');
  • registerThird页,通过点击registerThird的返回按钮,返回registerFirst页。
# 例如:myflutter(项目名)目录结构如下,修改“返回到上一级路由”代码
# - pages/
#   - tabs/
#     - user.dart【✔】
#     - home.dart
#     - setting.dart
#     - category.dart
#   - user/
#     - login.dart
#     - registerFirst.dart【✔】
#     - registerSecond.dart【✔】
#     - registerThird.dart【✔】
#   - tabs.dart【✔】
# - routes
#   - routes.dart【✔】
# - main.dart

(1) 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);

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Flutter中替换路由")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) user.dart

import 'package:flutter/material.dart';

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

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

class _UserPageState extends State<UserPage> {
  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(onPressed: () {
            print("执行跳转");
            Navigator.pushNamed(context, "/login");
          }, child: const Text("登录")),
          const SizedBox(height: 40),
          ElevatedButton(onPressed: () {
            Navigator.pushNamed(context, "/registerFirst");
          }, child: const Text("注册")),
        ],
      ),
    );
  }
}

(3) routes.dart

import '../pages/tabs.dart';
import '../pages/user/login.dart';
import 'package:flutter/material.dart';
import '../pages/user/registerThird.dart';
import '../pages/user/registerFirst.dart';
import '../pages/user/registerSecond.dart';

Map routes = {                                          // 定义一个Map类型的路由
  "/": (context) => const Tabs(),
  "/login": (context) => const LoginPage(),
  "/registerThird": (context) => const RegisterThirdPage(),
  "/registerFirst": (context) => const RegisterFirstPage(),
  "/registerSecond": (context) => const RegisterSecondPage(),
};

// 配置onGenerateRoute,固定写法,该方法相当于一个中间件,可以做权限判断
// ignore: prefer_function_declarations_over_variables
var onGenerateRoute = (RouteSettings settings) {
  final String? name = settings.name;                   // 统一处理
  final Function? pageContentBuilder = routes[name];
  if (pageContentBuilder != null) {
    if (settings.arguments != null) {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(
          context, arguments: settings.arguments
        )
      );
      return route;
    } else {
      final Route route = MaterialPageRoute(
        builder: (context) => pageContentBuilder(context)
      );
      return route;
    }
  }
  return null;
};

(4) registerFirst.dart

import 'package:flutter/material.dart';

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

  
  State<RegisterFirstPage> createState() => _RegisterFirstPageState();
}

class _RegisterFirstPageState extends State<RegisterFirstPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("注册第一步")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("注册第一步"),
            const SizedBox(height: 40),
            ElevatedButton(onPressed: () {
              Navigator.pushNamed(context, "/registerSecond");
            }, child: const Text("下一步")),
          ],
        ),
      ),
    );
  }
}

(5) registerThird.dart

import 'package:flutter/material.dart';

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

  
  State<RegisterThirdPage> createState() => _RegisterThirdPageState();
}

class _RegisterThirdPageState extends State<RegisterThirdPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("注册第三步")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("注册第三步"),
            const SizedBox(height: 40),
            ElevatedButton(onPressed: () {
            }, child: const Text("完成注册")),
          ],
        ),
      ),
    );
  }
}

(6) registerSecond.dart

import 'package:flutter/material.dart';

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

  
  State<RegisterSecondPage> createState() => _RegisterSecondPageState();
}

class _RegisterSecondPageState extends State<RegisterSecondPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("注册第二步")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("注册第二步"),
            const SizedBox(height: 40),
            ElevatedButton(onPressed: () {
              // 命名路由跳转
              // Navigator.pushNamed(context, "/registerThird");
              // 替换路由跳转
              Navigator.of(context).pushReplacementNamed("/registerThird");
            }, child: const Text("下一步")),
          ],
        ),
      ),
    );
  }
}

7-8 返回到根路由方法

  • 用户中心 > registerFirst > registerSecond > registerThird > 用户中心。
  • registerThird注册成功后返回用户中心,此时需要用返回到根路由的方法。
# 例如:myflutter(项目名)目录结构如下,修改“Flutter中替换路由”代码
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart
#     - setting.dart
#     - category.dart
#   - user/
#     - login.dart
#     - registerFirst.dart
#     - registerSecond.dart
#     - registerThird.dart【✔】
#   - tabs.dart【✔】
# - routes
#   - routes.dart
# - main.dart【✔】

(1) 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 {
  final int index;
  const Tabs({Key? key, this.index=0}) : super(key: key);

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

class _TabsState extends State<Tabs> {
  late int _currentIndex;
  
  void initState() {
    super.initState();
    _currentIndex=widget.index;
  }
  final List<Widget> _pages = const [
    HomePage(), CategoryPage(), SettingPage(), UserPage(),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("返回到根路由方法")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      // home: const Tabs(),
      initialRoute: "/login",                           // 初始化路由,首次运行加载登录页
      onGenerateRoute: onGenerateRoute,                 // 固定写法,配置onGenerateRoute
    );
  }
}

(3) registerThird.dart

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

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

  
  State<RegisterThirdPage> createState() => _RegisterThirdPageState();
}

class _RegisterThirdPageState extends State<RegisterThirdPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("注册第三步")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("注册第三步"),
            const SizedBox(height: 40),
            ElevatedButton(onPressed: () {
              Navigator.of(context).pushAndRemoveUntil( // 返回到根页面
                MaterialPageRoute(builder: (BuildContext context) {
                  return const Tabs(index: 3);          // index指定返回的页面
                }), (route) => false
              );
            }, child: const Text("完成注册")),
          ],
        ),
      ),
    );
  }
}

7-9 同风格的路由跳转

  • MaterialPageRoute组件可以使用和平台风格一致的路由切换动画。
  • 例如在iOS上会左右滑动切换,而在Android上则会上下滑动切换。
  • CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件。
  • 如果在Android上也想使用左右切换风格,可以使用CupertinoPageRoute。
  • 在iOS中appBarTheme的标题是自动居中的,而在Android中是自动居左的。
# 例如:myflutter(项目名)目录结构如下,修改“返回到根路由方法”代码
# - pages/
#   - tabs/
#     - user.dart
#     - home.dart
#     - setting.dart
#     - category.dart
#   - user/
#     - login.dart
#     - registerFirst.dart
#     - registerSecond.dart
#     - registerThird.dart
#   - tabs.dart【✔】
# - routes
#   - routes.dart【✔】
# - main.dart【✔】

(1) 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 {
  final int index;
  const Tabs({Key? key, this.index=0}) : super(key: key);

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

class _TabsState extends State<Tabs> {
  late int _currentIndex;
  
  void initState() {
    super.initState();
    _currentIndex=widget.index;
  }
  final List<Widget> _pages = const [
    HomePage(), CategoryPage(), SettingPage(), UserPage(),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("同风格的路由跳转")),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red,                         // 选中的颜色
        iconSize: 40,                                   // 配置底部菜单大小
        type: BottomNavigationBarType.fixed,            // 4个以上菜单需配置此项
        // 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: "设置",
          ),
          BottomNavigationBarItem(                      // 没有type菜单图标会被挤掉
            icon:Icon(Icons.people),
            label: "用户",
          ),
        ],
      ),
    );
  }
}

(2) main.dart

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,                // 去掉右上角debug图标
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        appBarTheme: const AppBarTheme(                 // 全局配置主题
          centerTitle: true                             // 主题标题全部居中显示
        ),
      ),
      // home: const Tabs(),
      initialRoute: "/login",                           // 初始化路由,首次运行加载登录页
      onGenerateRoute: onGenerateRoute,                 // 固定写法,配置onGenerateRoute
    );
  }
}

(3) routes.dart

import '../pages/tabs.dart';
import '../pages/user/login.dart';
import 'package:flutter/cupertino.dart';                // 配置iOS风格的路由,引入库
// import 'package:flutter/material.dart';
import '../pages/user/registerThird.dart';
import '../pages/user/registerFirst.dart';
import '../pages/user/registerSecond.dart';

Map routes = {                                          // 定义一个Map类型的路由
  "/": (context) => const Tabs(),
  "/login": (context) => const LoginPage(),
  "/registerThird": (context) => const RegisterThirdPage(),
  "/registerFirst": (context) => const RegisterFirstPage(),
  "/registerSecond": (context) => const RegisterSecondPage(),
};

// 配置onGenerateRoute,固定写法,该方法相当于一个中间件,可以做权限判断
// ignore: prefer_function_declarations_over_variables
var onGenerateRoute = (RouteSettings settings) {
  final String? name = settings.name;                   // 统一处理
  final Function? pageContentBuilder = routes[name];
  if (pageContentBuilder != null) {
    if (settings.arguments != null) {
      // final Route route = MaterialPageRoute(
      final Route route = CupertinoPageRoute(           // 替换MaterialPageRoute
        builder: (context) => pageContentBuilder(
          context, arguments: settings.arguments
        )
      );
      return route;
    } else {
      // final Route route = MaterialPageRoute(
      final Route route = CupertinoPageRoute(
        builder: (context) => pageContentBuilder(context)
      );
      return route;
    }
  }
  return null;
};

文章作者: bsf
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 bsf !
评论
 上一篇
有状态组件(二) 有状态组件(二)
Flutter有状态组件包括StatefulWidget和State,用于实现交互式功能,例如对话框、动态轮播、动画等。
2023-07-01
下一篇 
Flutter介绍 Flutter介绍
Flutter横跨Android、iOS、MacOS、Windows、Linux等多个系统,还可以打包成Web程序运行在浏览器上。
2023-03-05
  目录