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 应用主框架,采用了底部导航 + 页面切换的常见模式,实现了应用的主要导航功能,包括首页、自动模式、手动模式和会员中心等主要功能区域的切换。

相关推荐
小七_雪球1 分钟前
Permission denied"如何解决?详解GitHub SSH密钥认证流程
前端·github
野原猫之助2 分钟前
tailwind css在antd组件中使用不生效
前端
菜鸟码农_Shi4 分钟前
Node.js 如何实现 GitHub 登录(OAuth 2.0)
javascript·node.js
没资格抱怨9 分钟前
如何在vue3项目中使用 AbortController取消axios请求
前端·javascript·vue.js
掘金酱13 分钟前
😊 酱酱宝的推荐:做任务赢积分“拿”华为MatePad Air、雷蛇机械键盘、 热门APP会员卡...
前端·后端·trae
热爱编程的小曾24 分钟前
sqli-labs靶场 less 11
前端·css·less
丁总学Java30 分钟前
wget(World Wide Web Tool) 教程:Mac ARM 架构下安装与使用指南!!!
前端·arm开发·macos
总之就是非常可爱35 分钟前
🚀 使用 ReadableStream 优雅地处理 SSE(Server-Sent Events)
前端·javascript·后端
shoa_top1 小时前
Cookie、sessionStorage、localStorage、IndexedDB介绍
前端
鸿蒙场景化示例代码技术工程师1 小时前
实现文本场景化鸿蒙示例代码
前端