flutter底部导航代码解释

dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shirne_dialog/shirne_dialog.dart';

import '../utils/api.dart';
import '../utils/logger.dart';
import '../widgets/tags/empty.dart';
import '../application.dart';
import 'home/home.dart' show HomePage;
import 'channel/index.dart' as channel;
import 'member/index.dart' as member;

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

  @override
  State<IndexPage> createState() => _IndexPageState();
}

class _IndexPageState extends State<IndexPage> {
  int _lastTapBack = 0;
  int _selectedIndex = 0;
  late final PageController _controller =
      PageController(initialPage: _selectedIndex);

  bool initialized = false;
  bool isError = false;
  bool isReload = false;

  @override
  initState() {
    super.initState();
    _init();
  }

  @override
  dispose() {
    _controller.dispose();
    super.dispose();
  }

  _init() async {
    try {
      await app.login.restoreToken();
      await app.getSiteinfo();
      setState(() {
        initialized = true;
      });
    } on ServerException catch (_) {
      setState(() {
        isError = true;
      });
    }
  }

  void _onItemTapped(index) {
    if (index == 2) {
      int curPage = _controller.page?.round() ?? 0;
      setState(() {
        _selectedIndex = curPage > 1 ? (curPage + 1) : curPage;
      });
      app.route('/feedback');
    } else {
      index = index > 1 ? (index - 1) : index;
      setState(() {
        _selectedIndex = index;
        _controller.animateToPage(
          _selectedIndex,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeIn,
        );
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!initialized) {
      return AnimatedSwitcher(
        duration: const Duration(milliseconds: 300),
        child: isError ? renderError() : renderLoading(),
      );
    }

    return WillPopScope(
      onWillPop: () {
        int now = DateTime.now().millisecondsSinceEpoch;
        logger.info('onWillPop $_lastTapBack $now');
        if (now - _lastTapBack > 800) {
          _lastTapBack = now;
          MyDialog.toast(
            '再次点击返回退出应用',
            duration: const Duration(seconds: 1),
            style: MyDialog.theme.toastStyle?.bottom(),
          );
          return Future.value(false);
        } else {
          return Future.value(true);
        }
      },
      child: Scaffold(
        body: PageView(
          controller: _controller,
          onPageChanged: (index) {
            setState(() {
              _selectedIndex = index > 1 ? (index + 1) : index;
            });
          },
          children: const [
            HomePage(),
            channel.IndexPage(channel: 'blog'),
            channel.IndexPage(channel: 'works'),
            member.IndexPage(),
          ],
        ),
        bottomNavigationBar: BottomAppBar(
          shape: const CircularNotchedRectangle(),
          elevation: 16,
          child: BottomNavigationBar(
            backgroundColor: Colors.transparent,
            elevation: 0,
            items: const [
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.house),
                activeIcon: Icon(CupertinoIcons.house_fill),
                label: '首页',
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.settings_solid),
                activeIcon: Icon(CupertinoIcons.settings_solid),
                label: '自动',
              ),
              BottomNavigationBarItem(icon: SizedBox(), label: ''),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.hand_raised),
                activeIcon: Icon(CupertinoIcons.hand_raised_fill),
                label: '手动',
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.person_alt_circle),
                activeIcon: Icon(CupertinoIcons.person_alt_circle_fill),
                label: '会员',
              ),
            ],
            currentIndex: _selectedIndex,
            //selectedItemColor: Colors.blue,
            onTap: _onItemTapped,
            type: BottomNavigationBarType.fixed,
            //unselectedItemColor: Colors.grey,
            selectedFontSize: 12,
            unselectedFontSize: 12,
            showUnselectedLabels: true,
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            app.route('/feedback');
          },
          heroTag: 'feedback-page',
          child: const Icon(CupertinoIcons.paperplane),
        ),
        floatingActionButtonLocation:
            FloatingActionButtonLocation.miniCenterDocked,
      ),
    );
  }

  Widget renderError() {
    return InkWell(
      onTap: () {
        setState(() {
          initialized = false;
          isError = false;
          isReload = true;
        });
        _init();
      },
      child: const Center(
        child: TagEmpty(
          '网络连接错误,点击重试',
          icon: Icons.wifi_off,
        ),
      ),
    );
  }

  Widget renderLoading() {
    return Container(
      color: Colors.white,
      child: const Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}

这段代码是一个Flutter应用的主页面框架实现,以下是主要功能解释:

1. 基本结构

IndexPage 是一个 StatefulWidget,实现了带底部导航栏的主界面。

2. 导航系统

  • 底部有5个导航选项:首页、自动、中间浮动按钮、手动和会员。
  • 导航栏使用了自定义图标,比如设置图标表示"自动",手势图标表示"手动"。

3. 页面控制

  • 使用 PageView 控制不同页面的切换,可以通过滑动或点击底部导航栏切换。
  • 实现了平滑的页面过渡动画(持续300毫秒的 ease-in 动画)。

4. 初始化流程

  • 应用启动时会尝试恢复用户登录状态并获取站点信息。
  • 显示加载画面或错误画面,直到初始化完成。

5. 返回键处理

  • 实现了双击返回退出功能,防止用户意外退出应用。
  • 第一次点击返回会显示提示,800毫秒内再次点击才会退出。

6. UI 特点

  • 底部导航栏采用透明背景。
  • 中间有一个浮动按钮,指向反馈页面。
  • 使用了 CircularNotchedRectangle 形状,为浮动按钮留出位置。

7. 错误处理

  • 提供了友好的错误界面,允许用户点击重试。
  • 网络异常时会显示错误信息。

整体而言,这是一个标准的 Flutter 应用主框架,采用了底部导航 + 页面切换的常见模式,实现了应用的主要导航功能,包括首页、自动模式、手动模式和会员中心等主要功能区域的切换。

相关推荐
wuhen_n11 小时前
JavaScript数据结构深度解析:栈、队列与树的实现与应用
前端·javascript
我是一只puppy11 小时前
使用AI进行代码审查
javascript·人工智能·git·安全·源代码管理
颜酱11 小时前
从二叉树到衍生结构:5种高频树结构原理+解析
javascript·后端·算法
狗哥哥12 小时前
微前端路由设计方案 & 子应用管理保活
前端·架构
微祎_12 小时前
Flutter for OpenHarmony:形状拼图游戏开发全指南 - 基于Flutter CustomPaint的可拖拽矢量拼图实现与设计理念
flutter
前端大卫12 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘12 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare13 小时前
浅浅看一下设计模式
前端
Lee川13 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
不爱吃糖的程序媛13 小时前
解锁Flutter鸿蒙开发新姿势——flutter_ohfeatures插件集实战指南
flutter