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'),
                ),
        );
      }),
相关推荐
m0_74823364几秒前
RabbitMQ 进阶
android·前端·后端
不想有bug的小菜鸟9 分钟前
vue3使用iframe全屏展示pdf效果
前端·pdf
m0_7482386310 分钟前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
u01005596010 分钟前
前端代理,解决跨域问题讲解
前端
quitv15 分钟前
react脚手架配置别名
前端·javascript·react.js
m0_5287238124 分钟前
前端如何进行性能优化
前端·性能优化
化作繁星26 分钟前
在 Vue 3 中,如何缓存和复用动态组件
前端·vue.js·缓存
一粒沙-1 小时前
iOS 将GIF图分享至微信
前端·ios
graywen1 小时前
从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
前端
Gazer_S2 小时前
【现代前端框架中本地图片资源的处理方案】
前端·javascript·chrome·缓存·前端框架