flutter系列之:做一个会飞的菜单

news2024/12/23 5:01:28

文章目录

  • 简介
  • 定义一个菜单项目
  • 让menu动起来
  • 添加菜单内部的动画
  • 总结

简介

flutter中自带了drawer组件,可以实现通用的菜单功能,那么有没有一种可能,我们可以通过自定义动画来实现一个别样的菜单呢?

答案是肯定的,一起来看看吧。

定义一个菜单项目

因为这里的主要目的是实现菜单的动画,所以这里的菜单比较简单,我们的menu是一个StatefulWidget,里面就是一个Column组件,column中有四行诗:

  static const _menuTitles = [
    '迟日江山丽',
    '春风花草香',
    '泥融飞燕子',
    '沙暖睡鸳鸯',
  ];

    Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child:_buildContent()
    );
  }


  Widget _buildContent() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 16),
        ..._buildListItems()
      ],
    );
  }

  List<Widget> _buildListItems() {
    final listItems = <Widget>[];
    for (var i = 0; i < _menuTitles.length; ++i) {
      listItems.add(
         Padding(
            padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
            child: Text(
              _menuTitles[i],
              textAlign: TextAlign.center,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.w500,
              ),
            ),
      )
      );
    }
    return listItems;
  }

让menu动起来

怎么让menu动起来呢?我们需要给最外层的AnimateMenuApp添加一个AnimationController,所以需要在_AnimateMenuAppState添加SingleTickerProviderStateMixin的mixin,如下所示:

class _AnimateMenuAppState extends State<AnimateMenuApp>
    with SingleTickerProviderStateMixin {
  late AnimationController _drawerSlideController;

然后在initState中对_drawerSlideController进行初始化:

  void initState() {
    super.initState();

    _drawerSlideController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 150),
    );
  }

在让menu动起来之前,我们需要设计一下动画的样式。假如我们的动画是让menu从右向左飞出。那么我们可以使用FractionalTranslation来进行offset进行位置变换。

并且当菜单没有开启的时候,我们需要显示一个空的组件,这里用SizedBox来替代。

当菜单开启的时候,就执行这个FractionalTranslation的动画,所以我们的build方法需要这样写:

  Widget _buildDrawer() {
    return AnimatedBuilder(
      animation: _drawerSlideController,
      builder: (context, child) {
        return FractionalTranslation(
          translation: Offset(1.0 - _drawerSlideController.value, 0.0),
          child: _isDrawerClosed() ? const SizedBox() : const Menu(),
        );
      },
    );
  }

FractionalTranslation中的Offset是根据_drawerSlideController的value来进行变化的。

那么_drawerSlideController的value怎么变化呢?

我们定义一个_toggleDrawer方法,在点击菜单按钮的时候来触发这个方法,从而实现_drawerSlideController的value变化:

  void _toggleDrawer() {
    if (_isDrawerOpen() || _isDrawerOpening()) {
      _drawerSlideController.reverse();
    } else {
      _drawerSlideController.forward();
    }
  }

同时,我们定义下面几个判断菜单状态的方法:

  bool _isDrawerOpen() {
    return _drawerSlideController.value == 1.0;
  }

  bool _isDrawerOpening() {
    return _drawerSlideController.status == AnimationStatus.forward;
  }

  bool _isDrawerClosed() {
    return _drawerSlideController.value == 0.0;
  }

因为菜单图标需要根据菜单状态来发生改变,菜单的状态又是依赖于_drawerSlideController,所以,我们把IconButton放到一个AnimatedBuilder里面,从而实现动态变化的效果:

  PreferredSizeWidget _buildAppBar() {
    return AppBar(
      title: const Text(
        '动画菜单',
        style: TextStyle(
          color: Colors.black,
        ),
      ),
      backgroundColor: Colors.transparent,
      elevation: 0.0,
      automaticallyImplyLeading: false,
      actions: [
        AnimatedBuilder(
          animation: _drawerSlideController,
          builder: (context, child) {
            return IconButton(
              onPressed: _toggleDrawer,
              icon: _isDrawerOpen() || _isDrawerOpening()
                  ? const Icon(
                Icons.clear,
                color: Colors.black,
              )
                  : const Icon(
                Icons.menu,
                color: Colors.black,
              ),
            );
          },
        ),
      ],
    );
  }

最后实现的效果如下:

添加菜单内部的动画

上面的例子中整个菜单是作为一个整体来动画的,有没有可能菜单里面的每一个item也有自己的动画呢?

答案当然是肯定的。

我们只需要在上面的基础上将menu组件添加动画支持即可:

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin

动画中的位移我们选择使用Transform.translate,同时还添加了淡入淡出的效果,也就是把上面例子中的Padding用AnimatedBuilder包裹起来,如下所示:

  List<Widget> _buildListItems() {
    final listItems = <Widget>[];
    for (var i = 0; i < _menuTitles.length; ++i) {
      listItems.add(
        AnimatedBuilder(
          animation: _itemController,
          builder: (context, child) {
            final animationPercent = Curves.easeOut.transform(
              _itemSlideIntervals[i].transform(_itemController.value),
            );
            final opacity = animationPercent;
            final slideDistance = (1.0 - animationPercent) * 150;

            return Opacity(
              opacity: opacity,
              child: Transform.translate(
                offset: Offset(slideDistance, 0),
                child: child,
              ),
            );
          },
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
            child: Text(
              _menuTitles[i],
              textAlign: TextAlign.center,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ),
      );
    }
    return listItems;
  }

AnimatedBuilder中的builder返回的是一个Opacity对象,里面包含了opacity和child两个属性。其中最终要的一个变化值是animationPercent,这个值是根据_itemController的value和初始设置的各个item的变化时间来决定的。

每个item的值是不一样的:

  void _createAnimationIntervals() {
    for (var i = 0; i < _menuTitles.length; ++i) {
      final startTime = _initialDelayTime + (_staggerTime * i);
      final endTime = startTime + _itemSlideTime;
      _itemSlideIntervals.add(
        Interval(
          startTime.inMilliseconds / _animationDuration.inMilliseconds,
          endTime.inMilliseconds / _animationDuration.inMilliseconds,
        ),
      );
    }
  }

最后运行结果如下:

总结

在flutter中一切皆可动画,我们只需要掌握动画创作的诀窍即可。

本文的例子:https://github.com/ddean2009/learn-flutter.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(栈和队列) 150. 逆波兰表达式求值 ——【Leetcode每日一题】

❓150. 逆波兰表达式求值 难度&#xff1a;中等 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意&#xff1a; 有效的算符为 ‘’、‘-’、‘*’ 和 ‘/’ 。每个操作数&#…

vue制作自己的组件库(仿ElementUI)

1.首先自己创建个新的vue项目&#xff0c;之后更改下目录形式&#xff0c;将src文件更改为examples&#xff0c;这里是专门放组件展示的md文件&#xff0c;packages文件里是放自己写的组件代码 2.然后是开始配置vue.config.js文件 &#xff0c;其中md-loader是读取md文件的相关…

Linux下安装docker教程

目录 一、安装CentOS系统 二、安装Docker 1.卸载之前安装过的旧版本&#xff1a; 2.安装docker 3.启动docker 4.配置docker镜像仓库 三、Docker的使用 1.docker容器常用指令&#xff1a; 2.运行案例&#xff1a;docker拉取并运行nignx 3.查看容器日志 一、安装CentOS系…

Prompt工程-高级提示

高阶Prompting 到这一步&#xff0c;应该很明显&#xff0c;改进提示有助于在不同任务上获得更好的结果。这就是Prompt工程背后的整个理念。 虽然之前的例子很有趣&#xff0c;但在我们深入了解更高级的概念之前&#xff0c;让我们先正式地介绍一些概念。 文章目录 高阶Promp…

使用Flexible实现移动端页面的终端适配,及快捷设置开发工具px转换rem

1、使用Flexible实现移动端页面的终端适配 官网GitHub地址API介绍&#xff1a; ​​​​​​使用Flexible实现手淘H5页面的终端适配 Issue #17 amfe/article GitHub 阿里 cdn 引入地址&#xff1a; <script src"http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??…

JavaWeb公司员工管理系统

1.需求分析 系统角色分别为&#xff1a;最高权限管理员、人力主管、部门主管、员工。总体业务流程图如下图所示。 用例图如下所示。 2.系统设计 系统功能总体设计如下图。 数据库设计如下图所示。 3.系统实现效果 登录功能实现效果如下图所示。 考勤管理模块实现效…

字节同事问我:我的Postman为什么连不了数据库?

postman本身没有数据库连接功能&#xff0c;所以用到了node.js中的xmysql实现Rest API的生成&#xff0c;利用postman进行请求&#xff0c;获取需要的数据&#xff0c;来做数据准备或断言 1 安装 安装node.js&#xff1a;要求版本大于等于7.6 首先保证你的环境上有node.js&…

云计算基础

一、分布式计算 分布式计算将应用分解成许多更小的部分&#xff0c;分配到多台计算机进行处理&#xff0c;这样可以节省整体计算时间&#xff0c;大大提高计算效率。 云计算是分布式计算技术的一种&#xff0c;也是分布式计算这种科学概念的商业实现。分布式计算的优点就是发…

chatgpt赋能python:Python如何分割列表

Python如何分割列表 介绍 在Python编程中&#xff0c;列表是一种非常常见的数据类型。有时候我们需要将一个大的列表分割成几个小的列表&#xff0c;以便更好地处理数据。Python提供了多种方法来实现这个目的。在本文中&#xff0c;我们将介绍Python中如何分割列表的几种方法…

深度学习技巧应用18-OFD格式文件与人工智能结合的技巧应用,实现OFD转文本、OFD自动分类与内容提取

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用18-OFD格式文件与人工智能结合的技巧应用,实现OFD转文本、OFD自动分类与内容提取。OFD格式文件被称为“中国版PDF”。它与PDF格式类似,是一种可读、可打印、可编辑、可存档的电子文档格式,但OFD格式相对于PDF格式更加…

pandas

pandas 数据结构生成数据创建 Series创建 DataFrame 数据处理相关方法功能介绍 数据结构 描述举例Series带有标签的一维数组DataFrame带有标签的二维数组 生成数据 创建 Series # s pd.Series(data, indexindex) s1 pandas.Series([张三,李四,王五,郑六]) s2 pandas.Seri…

电动力学专题:辐射的频谱分析

辐射的频谱分析 韧致辐射就是带电粒子入射到物质靶上时&#xff0c;它和靶内原子中的电子和原子核碰撞&#xff0c;在碰撞过程减速而产生的辐射。X射线的连续谱部分就体现了韧致辐射的性质。在这个过程中的带电粒子速度远小于光速 低速运动带电粒子在碰撞过程中的辐射频谱 频…

设备指纹系列--前端篇

基础篇请看&#xff1a;设备指纹系列–基础篇 我们接着前文继续写关于设备指纹前端接入方面的内容。话不多说&#xff0c;直接步入正题。 我们会在下文展示5种前端接入的方式&#xff0c;包括web接入、安卓接入、ios接入、微信小程序接入以及支付宝小程序接入。 Web接入 第…

SpringBoot整合Mybatis-Plus多数据源

一、前言 随着业务的不断扩展和复杂度的增加&#xff0c;我们在开发过程中往往需要访问多个数据库。 比如&#xff1a; 我们可能需要同时访问主数据库和从数据库&#xff0c;或者访问多个独立的数据库来处理不同的业务逻辑。这时候&#xff0c;我们就需要使用多数据源来实现对…

【python脚本】编写

这里写自定义目录标题 欢迎使用python来编写脚本环境搭建 欢迎使用python来编写脚本 测试方向&#xff0c;测试报告&#xff0c;单元测试 环境搭建 python环境搭建 下载地址 https://www.python.org/ 文档 https://docs.python.org/3/ pycharm的环境 使用chatgpt来实现代码功…

来了解一下白盒测试,黑盒测试,灰盒测试吧(超详解~)

根据被测对象的不同&#xff0c;软件测试可以分为白盒测试、黑盒测试、灰盒测试三种方式。那么&#xff0c;这三种测试方式具体是如何运行的&#xff1f;各有什么特点&#xff1f;下面&#xff0c;跟着静姐一起了解一下吧&#xff01; 01、白盒测试 WHITE BOX ●概念&#x…

实训第二天

创建数据库指定字符集 create database firstdb default character set utf8; 主键约束&#xff08;primary key&#xff09;不能为空&#xff0c;唯一约束(unique key)可以为空&#xff0c;但只允许一个空值 查看表结构 desc 表名 主表从表 被引用的表是主表 比如班级…

管理类联考•逻辑——解题技巧汇总

管理类联考•逻辑——解题技巧汇总 第一部分 形式逻辑 第1章 复言命题 母题1 充分与必要 充分条件 A是B的充分条件,记作A→B,读作“A推B”,是指假如事件A发生了,事件B一定发生。典型关联词: “如果…那么…。” 必要条件 A是B的必要条件,记作B→A,说明A的发生对于B的发生是…

电脑如何通过手机上网?

有时我们的电脑会出现没有网络&#xff0c;或者断网的现象&#xff0c;这时如果必须使用电脑&#xff0c;我们可以通过手机流量来上网&#xff0c;那么要如何操作呢&#xff1f;下面我们就来了解一下。 方法1. 电脑连接手机热点上网 该方法适用于笔记本电脑和有无线网卡的台式…

安全响应中心 — 垃圾邮件事件报告(6.5)

2023年6月 第二周 样本概况 ✅ 类型1&#xff1a; 携带钓鱼链接的伪造传票邮(URLPhish) 近期&#xff0c;安全团队捕获到一类新的伪造51某票的钓鱼邮件&#xff0c;内容上为伪造的律师事务所传票信息&#xff0c;并诱导收件人点击钓鱼链接。代表样本如下&#xff1a; 结合情…