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

相关推荐
excel4 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子11 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构18 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep19 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss23 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风24 分钟前
html二次作业
前端·html
江城开朗的豌豆27 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵27 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮31 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf
江城开朗的豌豆37 分钟前
拆解Redux:从零手写一个状态管理器,彻底搞懂它的魔法!
前端·javascript·react.js