flutter 自定义菜单按钮展开部件View

实现效果

  floatbutton的菜单展开在开发中还是比较常用的一种方式,因为flutter官方没有专门去实现这么一个效果,我这里就写了一个View能够实现菜单的收缩和展开以及提供部分的自定义。
在这里插入图片描述

View代码

  以下便是整体View的实现代码,可直接复制到新的dart文件里使用,原理是基于Stack做为空间上的层叠,然后通过transform进行子菜单自身的移动,移动方向由type决定,然后再根据自身排序计算移动数值,因为超出本身区域无法点击,我这边暂时默认处理方式是会计算拓展最大值然后加入SizeBox来填充,所以在效果布局上会是展开后的大小,后续如果找到能够不填充也能获取点击事件分发的方式就进一步做优化。
提供的Api参数如下:

  • 按钮图标 List iconList; //这个必填,其他选填
  • 按钮高度 double? fabHeight;
  • 主菜单按钮图标大小 double? iconsize;
  • 选项卡按钮间隔 double? tabspace;
  • 选项卡按钮颜色 Color? tabcolor;
  • 主菜单卡收起后颜色 Color?
  • 主菜单卡展开后颜色 MainTabBeginColor;
  • 主菜单卡展开前颜色 Color? MainTabAfterColor;
  • 主菜单卡变化图标(动画图标) AnimatedIconData? MainAnimatedIcon;
  • 选项卡类型即展开方向 ButtonType type;         Left//向左, Right//向右, Top//向上, Bottom //向下
import 'package:FFFFF/tools/ColorsPick.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';

/**
 * created by:number9
 * time:2021.7.02
 */
class FloatExpendButton extends StatefulWidget {
  //按钮图标
  List<Icon> iconList;

  //按钮高度
  double? fabHeight;

  //主菜单按钮图标大小
  double? iconsize;

  //选项卡按钮间隔
  double? tabspace;

  //选项卡按钮颜色
  Color? tabcolor;

  //主菜单卡收起后颜色
  Color? MainTabBeginColor;

  //主菜单卡展开后颜色
  Color? MainTabAfterColor;

  //主菜单卡变化图标(动画图标)
  AnimatedIconData? MainAnimatedIcon;
  
  //选项卡类型即展开方向
  ButtonType type;
  
  //按钮的点击事件
  final Function(int index) callback;

  FloatExpendButton(this.iconList,
      {required this.callback,
      this.fabHeight = 30,
      this.tabspace = 10,
      this.tabcolor = Colors.blue,
      this.MainTabBeginColor = Colors.red,
      this.MainTabAfterColor = Colors.grey,
      this.MainAnimatedIcon = AnimatedIcons.menu_close,
      this.iconsize = 15,
      this.type = ButtonType.Left});

  @override
  _FloatExpendState createState() => _FloatExpendState();
}

//旋转变换按钮 向上弹出的效果 State实现
class _FloatExpendState extends State<FloatExpendButton>
    with SingleTickerProviderStateMixin {
  //记录是否打开
  bool isOpened = false;

  //动画控制器
  late AnimationController _animationController;

  //颜色变化取值
  late Animation<Color?> _animateColor;

  //图标变化取值
  late Animation<double> _animateIcon;

  //按钮的位置动画
  late Animation<double> _fabtween;

  //动画执行速率
  Curve _curve = Curves.easeOut;

  @override
  initState() {
    super.initState();
    //初始化动画控制器
    _animationController =
        AnimationController(vsync: this, duration: Duration(milliseconds: 300));
    //添加动画监听
    _animationController.addListener(() {
      setState(() {});
    });
    //Tween结合_animationController,使300毫秒内执行一个从0.0到0.1的变换过程
    _animateIcon =
        Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
    //结合_animationController 实现一个从Colors.blue到Colors.deepPurple的动画过渡
    _animateColor = ColorTween(
      begin: widget.MainTabBeginColor,
      end: widget.MainTabAfterColor,
    ).animate(CurvedAnimation(parent: _animationController, curve: _curve));

    _fabtween = Tween<double>(
      begin: 0,
      end: _getfabtweenAfter(),
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Interval(
        0.0,
        0.75,
        curve: _curve,
      ),
    ));
  }

  //根据类型获取变化结束值
  double _getfabtweenAfter() {
    if (widget.type == ButtonType.Right || widget.type == ButtonType.Bottom) {
      return widget.fabHeight! + widget.tabspace!;
    } else {
      return -(widget.fabHeight! + widget.tabspace!);
    }
  }

  //根据类型获取X轴移动数值
  double _getfabtranslateX(int i) {
    if (widget.type == ButtonType.Left || widget.type == ButtonType.Right) {
      return _fabtween.value * (i + 1);
    } else {
      return 0;
    }
  }

  //根据类型获取Y轴移动数值
  double _getfabtranslateY(int i) {
    if (widget.type == ButtonType.Top || widget.type == ButtonType.Bottom) {
      return _fabtween.value * (i + 1);
    } else {
      return 0;
    }
  }

  //根据类型获取主菜单位置
  AlignmentGeometry _getAligment() {
    if (widget.type == ButtonType.Top) {
      return Alignment.bottomCenter;
    } else if (widget.type == ButtonType.Left) {
      return Alignment.centerRight;
    } else if (widget.type == ButtonType.Bottom) {
      return Alignment.topCenter;
    } else {
      return Alignment.centerLeft;
    }
  }

  @override
  Widget build(BuildContext context) {
    //构建子菜单
    List<Widget> itemList = [];

    for (int i = 0; i < widget.iconList.length; i++) {
      //通过Transform来促成FloatingActionButton的平移
      itemList.add(
        Transform(
          transform: Matrix4.translationValues(
              _getfabtranslateX(i), _getfabtranslateY(i), 0.0),
          child: Container(
            width: widget.fabHeight,
            height: widget.fabHeight,
            //margin: EdgeInsets.only(left: 10),
            child: FloatingActionButton(
              heroTag: "$i",
              elevation: 0.5,
              backgroundColor: widget.tabcolor,
              onPressed: () {
                // //点击菜单子选项要求菜单弹缩回去
                //if(isOpened){
                floatClick();
                if (widget.callback != null) {
                  widget.callback(i);
                }
                //}
              },
              child: Icon(
                widget.iconList[i].icon,
                key: widget.iconList[i].key,
                size: widget.iconList[i].size,
                semanticLabel: widget.iconList[i].semanticLabel,
                textDirection: widget.iconList[i].textDirection,
              ),
            ),
          ),
        ),
      );
    }

    return Stack(
      alignment: _getAligment(),
      children: [
        widget.type == ButtonType.Left || widget.type == ButtonType.Right
            ? SizedBox(
                width: (widget.fabHeight! + widget.tabspace!) *
                        widget.iconList.length +
                    widget.fabHeight!)
            : SizedBox(
                height: (widget.fabHeight! + widget.tabspace!) *
                        widget.iconList.length +
                    widget.fabHeight!),
      ]//根据横纵情况拓展点击区域
        ..addAll(itemList)
        ..add(Positioned(
          child: floatButton(),
        )),
    );
  }

  //构建固定旋转菜单按钮
  Widget floatButton() {
    return Container(
      width: widget.fabHeight,
      height: widget.fabHeight,
      child: FloatingActionButton(
        //通过_animateColor实现背景颜色的过渡
        backgroundColor: _animateColor.value, // _animateColor.value
        onPressed: floatClick,
        elevation: 0.5,
        //通过AnimatedIcon实现标签的过渡
        child: AnimatedIcon(
          icon: widget.MainAnimatedIcon!,
          size: widget.iconsize,
          progress: _animateIcon,
        ),
      ),
    );
  }

  //FloatingActionButton的点击事件,用来控制按钮的动画变换
  floatClick() {
    if (!isOpened) {
      _animationController.forward(); //展开动画
    } else {
      _animationController.reverse(); //收回动画
    }
    isOpened = !isOpened;
  }

  //页面销毁时,销毁动画控制器
  @override
  dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

enum ButtonType { Left, Right, Top, Bottom }

实例代码

效果图实例的使用代码如下所示,可参照进行使用

import 'package:FFFFF/view/fab/fabButton.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(new MyApp());
  SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    context.widget.key;
    return new MaterialApp(
      title: 'Welcome to Flutter',
      color: Colors.white,
      home: Test(),
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
    );
  }
}

class Test extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _Test();
  }
}

class _Test extends State<Test> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Stack(
        children: [
          Align(
            alignment: Alignment.center,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                FloatExpendButton(
                  //菜单图标组
                  [
                    Icon(
                      Icons.add,
                      size: 10,
                    ),
                    Icon(Icons.share, size: 15)
                  ],
                  //点击事件回调
                  callback: (int index) {
                    print("点击");
                    print(index);
                  },
                  tabcolor: Colors.yellow,
                  MainTabBeginColor: Colors.black,
                  MainTabAfterColor: Colors.blue,
                  fabHeight: 30,
                  tabspace: 5,
                  type: ButtonType.Top,
                ),
                SizedBox(
                  height: 20,
                ),
                FloatExpendButton(
                  //菜单图标组
                  [
                    Icon(
                      Icons.add,
                      size: 15,
                    ),
                    Icon(Icons.share, size: 15)
                  ],
                  //点击事件回调
                  callback: (int index) {
                    print("点击");
                    print(index);
                  },
                  tabcolor: Colors.red,
                  MainTabBeginColor: Colors.green,
                  MainTabAfterColor: Colors.black45,
                  fabHeight: 50,
                  tabspace: 10,
                  type: ButtonType.Left,
                ),
                SizedBox(
                  height: 20,
                ),
                FloatExpendButton(
                  //菜单图标组
                  [
                    Icon(
                      Icons.add,
                      size: 15,
                    ),
                    Icon(Icons.share, size: 20)
                  ],
                  //点击事件回调
                  callback: (int index) {
                    print("点击");
                    print(index);
                  },
                  tabcolor: Colors.green,
                  MainTabBeginColor: Colors.black,
                  MainTabAfterColor: Colors.blue,
                  fabHeight: 60,
                  tabspace: 5,
                  type: ButtonType.Right,
                ),
                SizedBox(
                  height: 20,
                ),
                FloatExpendButton(
                  //菜单图标组
                  [
                    Icon(
                      Icons.add,
                      size: 15,
                    ),
                    Icon(Icons.share, size: 15)
                  ],
                  //点击事件回调
                  callback: (int index) {
                    print("点击");
                    print(index);
                  },
                  tabcolor: Colors.blue,
                  MainTabBeginColor: Colors.deepOrange,
                  MainTabAfterColor: Colors.orangeAccent,
                  fabHeight: 30,
                  tabspace: 15,
                  type: ButtonType.Bottom,
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值