Flutter如何实现底部导航中间按钮的凸起,以及切换导航时的动画效果

前置知识

  • 了解flutter基础知识
  • 在Scaffold组件中有bottomNavigationBar可以设置底部导航
  • 使用BottomNavigationBar数量超过3个时会显示白屏,需要将type设置为BottomNavigationBarType.fixed

设置中间凸起的底部导航

思路

  • 中间凸起效果:利用浮动按钮居中,覆盖中间的BottomNavigationBarItem
  • 关于BottomNavigationBar的样式,后期会抽到全局的主题进行管理。本文没有抽离

效果

代码

/pages/Index_page.dart

dart 复制代码
import 'package:flutter/material.dart';
class IndexPage extends StatelessWidget {
  const IndexPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed, items: _buildBottomBarItem),
      // 浮动按钮
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.transparent,
        elevation: 0,
        // 设置为圆形
        shape: const CircleBorder(),
        onPressed: () {},
        child: const Icon(
          Icons.change_circle,
          size: 55,
        ),
      ),
      // 设置浮动按钮位置底部居中
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // 生成底部导航按钮
  List<BottomNavigationBarItem> get _buildBottomBarItem {
    return [
      const BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
      const BottomNavigationBarItem(icon: Icon(Icons.search), label: '搜索'),
      // 中间这个使用空白占位
      const BottomNavigationBarItem(icon: SizedBox(height: 24), label: '交易'),
      const BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
      const BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
    ];
  }
}

切换时的弹跳动画效果

思路

  • 设置动画需要AnimationController,实例AnimationController需要this,因此使用StatefulWidget有状态组件,在initState中实例化AnimationController
  • 使用AnimationController需要混入SingleTickerProviderStateMixin类
  • 利用AnimatedBuilder监听_animationController的变化
  • 将_animationController转换为Animation,即可使用Animation的value设置激活的图标的大小,通过AnimatedBuilder的监听不断build让页面呈现动画效果
  • 在bar切换时执行动画

效果

代码

/pages/Index_page.dart

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

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

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

// 使用AnimationController需要混入SingleTickerProviderStateMixin类
class _IndexPageState extends State<IndexPage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  late final Animation<double> _animation;
  int _currentIndex = 0;

  @override
  void initState() {
    super.initState();
    // 动画Controller,可以控制动画的开始,停止等操作,首次先执行一次动画
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500))
      ..forward();
    // Curves.bounceOut弹跳退出效果
    _animation = Tween<double>(begin: 1, end: 24)
        .chain(CurveTween(curve: Curves.bounceOut))
        .animate(_animationController);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('底部导航设置')),
      // AnimatedBuilder监听_animationController的变化
      bottomNavigationBar: AnimatedBuilder(
          animation: _animationController,
          builder: (context, child) {
            return BottomNavigationBar(
              currentIndex: _currentIndex,
              type: BottomNavigationBarType.fixed,
              onTap: (value) {
                _currentIndex = value;
                // 需要先清除上一个动画,forward才能生效,不让动画已经解释了,此时的value不会变了
                _animationController.reset();
                _animationController.forward();
              },
              items: _buildBottomBarItem,
            );
          }),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.transparent,
        elevation: 0,
        // 设置为圆形
        shape: const CircleBorder(),
        onPressed: () {},
        child: const Icon(
          Icons.change_circle,
          size: 55,
        ),
      ),
      // 设置浮动按钮位置底部居中
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // 生成底部导航按钮
  List<BottomNavigationBarItem> get _buildBottomBarItem {
    return [
      BottomNavigationBarItem(
          icon: Icon(
            Icons.person,
          ),
          activeIcon: Icon(
            Icons.person,
            size: _animation.value,
          ),
          label: '首页'),
      BottomNavigationBarItem(
          icon: Icon(
            Icons.person,
          ),
          activeIcon: Icon(
            Icons.person,
            size: _animation.value,
          ),
          label: '搜索'),
      // 中间这个使用空白占位
      const BottomNavigationBarItem(icon: SizedBox(height: 24), label: '交易'),
      BottomNavigationBarItem(
          icon: Icon(
            Icons.person,
          ),
          activeIcon: Icon(
            Icons.person,
            // 设置激活图标的大小
            size: _animation.value,
          ),
          label: '我的'),
      BottomNavigationBarItem(
          icon: Icon(
            Icons.person,
          ),
          activeIcon: Icon(
            Icons.person,
            size: _animation.value,
          ),
          label: '我的'),
    ];
  }
}

让中间的按钮旋转起来

效果

第一步:设置旋转效果的animation

dart 复制代码
@override
void initState() {
  super.initState();
  //...
  // 交易按钮旋转效果的animation
  _animationRotate = Tween<double>(begin: 0, end: 1)
    .chain(CurveTween(curve: Curves.linear))
    .animate(_animationController);
}

第二步:用AnimatedBuilder监听浮动按钮

dart 复制代码
floatingActionButton: AnimatedBuilder(
      animation: _animationController,
      builder: (context, child) {
        return FloatingActionButton(
          backgroundColor: Colors.transparent,
          elevation: 0,
          // 设置为圆形
          shape: const CircleBorder(),
          onPressed: () {
            _currentIndex = 2;
            // 执行动画
            _animationController.reset();
            _animationController.forward();
          },
          child: _currentIndex != 2
              ? const SvgIcon(icon: 'transaction')
          // 激活的时候进行旋转 SvgIcon是自己封装的组件可替换
              : Transform.rotate(
                  angle: math.pi * _animationRotate.value,
                  child: const SvgIcon(icon: 'transaction_active'),
                ),
        );
      }),
相关推荐
谭光志5 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
私人珍藏库5 小时前
[Android] 会计快题库 -财会职称考试刷题学习
android·人工智能·学习·app·软件·多功能
仿生狮子7 小时前
别再说“全栈”了,AI 时代团队只认这 5 种人
前端·人工智能·后端
kyriewen7 小时前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
Highcharts.js7 小时前
软件开发公司为什么选择 Highcharts?
前端·前端框架·echarts·数据可视化·技术选型·highcharts·图表工具
阿祖zu8 小时前
企业 AI 转型之痛,个人提效十倍不止,组织效率仍止步不前?
前端·aigc·agent
摇滚侠9 小时前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
酸梅果茶9 小时前
【6】lightning_lm项目-LIO 前端 -点云预处理模块
前端·slam
Hilaku9 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员
咖啡无伴侣10 小时前
unplugin-auto-import 使用详解+面试高频考点
前端·架构