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'),
                ),
        );
      }),
相关推荐
码爸10 分钟前
flink doris批量sink
java·前端·flink
深情废杨杨11 分钟前
前端vue-父传子
前端·javascript·vue.js
Flutter社区24 分钟前
使用 Flutter 3.19 更高效地开发
flutter·dart
J不A秃V头A1 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂2 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
Forever不止如此3 小时前
【CustomPainter】绘制圆环
flutter·custompainter·圆环
wills7774 小时前
Flutter Error: Type ‘UnmodifiableUint8ListView‘ not found
flutter