简介

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

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

定义一个菜单项目

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

  1. static const _menuTitles = [
  2. '迟日江山丽',
  3. '春风花草香',
  4. '泥融飞燕子',
  5. '沙暖睡鸳鸯',
  6. ];
  7. Widget build(BuildContext context) {
  8. return Container(
  9. color: Colors.white,
  10. child:_buildContent()
  11. );
  12. }
  13. Widget _buildContent() {
  14. return Column(
  15. crossAxisAlignment: CrossAxisAlignment.start,
  16. children: [
  17. const SizedBox(height: 16),
  18. ..._buildListItems()
  19. ],
  20. );
  21. }
  22. List<Widget> _buildListItems() {
  23. final listItems = <Widget>[];
  24. for (var i = 0; i < _menuTitles.length; ++i) {
  25. listItems.add(
  26. Padding(
  27. padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
  28. child: Text(
  29. _menuTitles[i],
  30. textAlign: TextAlign.center,
  31. style: const TextStyle(
  32. fontSize: 24,
  33. fontWeight: FontWeight.w500,
  34. ),
  35. ),
  36. )
  37. );
  38. }
  39. return listItems;
  40. }

让menu动起来

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

  1. class _AnimateMenuAppState extends State<AnimateMenuApp>
  2. with SingleTickerProviderStateMixin {
  3. late AnimationController _drawerSlideController;

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

  1. void initState() {
  2. super.initState();
  3. _drawerSlideController = AnimationController(
  4. vsync: this,
  5. duration: const Duration(milliseconds: 150),
  6. );
  7. }

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

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

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

  1. Widget _buildDrawer() {
  2. return AnimatedBuilder(
  3. animation: _drawerSlideController,
  4. builder: (context, child) {
  5. return FractionalTranslation(
  6. translation: Offset(1.0 - _drawerSlideController.value, 0.0),
  7. child: _isDrawerClosed() ? const SizedBox() : const Menu(),
  8. );
  9. },
  10. );
  11. }

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

那么_drawerSlideController的value怎么变化呢?

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

  1. void _toggleDrawer() {
  2. if (_isDrawerOpen() || _isDrawerOpening()) {
  3. _drawerSlideController.reverse();
  4. } else {
  5. _drawerSlideController.forward();
  6. }
  7. }

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

  1. bool _isDrawerOpen() {
  2. return _drawerSlideController.value == 1.0;
  3. }
  4. bool _isDrawerOpening() {
  5. return _drawerSlideController.status == AnimationStatus.forward;
  6. }
  7. bool _isDrawerClosed() {
  8. return _drawerSlideController.value == 0.0;
  9. }

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

  1. PreferredSizeWidget _buildAppBar() {
  2. return AppBar(
  3. title: const Text(
  4. '动画菜单',
  5. style: TextStyle(
  6. color: Colors.black,
  7. ),
  8. ),
  9. backgroundColor: Colors.transparent,
  10. elevation: 0.0,
  11. automaticallyImplyLeading: false,
  12. actions: [
  13. AnimatedBuilder(
  14. animation: _drawerSlideController,
  15. builder: (context, child) {
  16. return IconButton(
  17. onPressed: _toggleDrawer,
  18. icon: _isDrawerOpen() || _isDrawerOpening()
  19. ? const Icon(
  20. Icons.clear,
  21. color: Colors.black,
  22. )
  23. : const Icon(
  24. Icons.menu,
  25. color: Colors.black,
  26. ),
  27. );
  28. },
  29. ),
  30. ],
  31. );
  32. }

最后实现的效果如下:

添加菜单内部的动画

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

答案当然是肯定的。

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

  1. class _MenuState extends State<Menu> with SingleTickerProviderStateMixin

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

  1. List<Widget> _buildListItems() {
  2. final listItems = <Widget>[];
  3. for (var i = 0; i < _menuTitles.length; ++i) {
  4. listItems.add(
  5. AnimatedBuilder(
  6. animation: _itemController,
  7. builder: (context, child) {
  8. final animationPercent = Curves.easeOut.transform(
  9. _itemSlideIntervals[i].transform(_itemController.value),
  10. );
  11. final opacity = animationPercent;
  12. final slideDistance = (1.0 - animationPercent) * 150;
  13. return Opacity(
  14. opacity: opacity,
  15. child: Transform.translate(
  16. offset: Offset(slideDistance, 0),
  17. child: child,
  18. ),
  19. );
  20. },
  21. child: Padding(
  22. padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
  23. child: Text(
  24. _menuTitles[i],
  25. textAlign: TextAlign.center,
  26. style: const TextStyle(
  27. fontSize: 24,
  28. fontWeight: FontWeight.w500,
  29. ),
  30. ),
  31. ),
  32. ),
  33. );
  34. }
  35. return listItems;
  36. }

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

每个item的值是不一样的:

  1. void _createAnimationIntervals() {
  2. for (var i = 0; i < _menuTitles.length; ++i) {
  3. final startTime = _initialDelayTime + (_staggerTime * i);
  4. final endTime = startTime + _itemSlideTime;
  5. _itemSlideIntervals.add(
  6. Interval(
  7. startTime.inMilliseconds / _animationDuration.inMilliseconds,
  8. endTime.inMilliseconds / _animationDuration.inMilliseconds,
  9. ),
  10. );
  11. }
  12. }

最后运行结果如下:

总结

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

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

flutter系列之:做一个会飞的菜单的更多相关文章

  1. flutter系列之:创建一个内嵌的navigation

    目录 简介 搭建主Navigator 构建子路由 总结 简介 我们在flutter中可以使用Navigator.push或者Navigator.pushNamed方法来向Navigator中添加不同的 ...

  2. python爬虫系列:做一个简单的动态代理池

    自动 1.设置动态的user agent import urllib.request as ure import urllib.parse as upa import random from bs4 ...

  3. 做一个会PS切图的前端开发

    系列链接 做一个会使用PS的前端开发 做一个会PS切图的前端开发 切图方法分类 PhotoShop从CS版本演变到现在的CC版本,切图功能发生了比较大的变化,我们可以把PhotoShop CS版本时的 ...

  4. 如何用Mockplus快速做一个手风琴菜单?

    手风琴菜单是一种比较常用的菜单形式,利用原型工具来做这种菜单通常要用到中继器.即使是功能强大的Axure,想实现该效果也比较麻烦.但如果你对Mockplus有所了解,你一定知道,利用Mockplus的 ...

  5. SLAM+语音机器人DIY系列:(三)感知与大脑——6.做一个能走路和对话的机器人

    摘要 在我的想象中机器人首先应该能自由的走来走去,然后应该能流利的与主人对话.朝着这个理想,我准备设计一个能自由行走,并且可以与人语音对话的机器人.实现的关键是让机器人能通过传感器感知周围环境,并通过 ...

  6. 【 D3.js 入门系列 --- 5.1 】 做一个带坐标轴和标签的图表

    前面几节讲解了图标.坐标轴.比例等等,这一节整合这些内容做一个实用的图表.结果图如下: 代码如下所示: <html> <head> <meta charset=" ...

  7. 【 D3.js 入门系列 --- 3 】 做一个简单的图表!

    前面说了几节,都是对文字进行处理,这一节中将用 D3.js 做一个简单的柱形图. 做柱形图有很多种方法,比如用 HTML 的 div 标签,或用 svg . 推荐用 SVG 来做各种图形.SVG 意为 ...

  8. ADO.NET学习系列(三)----做一个登录案例

    总体思路.根据用户输入的用户名和密码,来判断,和数据库里面存的是不是一样,如果一样就表明登录成功,否则就登录失败. 方案一: 1.select* from 表名 where username=&quo ...

  9. 【 D3.js 入门系列 — 3 】 做一个简单的图表!

    图1. 柱形图 1. 柱形图 前几章的例子,都是对文字进行处理.本章中将用 D3 做一个简单的柱形图.制作柱形图有很多种方法,比如用 HTML 的 <div> 标签,或在 SVG 上绘制 ...

  10. 【Bugly干货分享】一起用 HTML5 Canvas 做一个简单又骚气的粒子引擎

    Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言 好吧,说是“粒子引擎”还是大言不 ...

随机推荐

  1. AlphaFold2中的残基刚体表示

    技术背景 在前面的这一篇博客中,比较全面的介绍了组成蛋白质的各种氨基酸的三维结构.由于每个氨基酸大小不一,在传统的蛋白质折叠预测的方案中,一般会考虑全原子方案或者是粗粒化方案.对于全原子方案而言,即时 ...

  2. Windows Server 远程桌面(RDP)使用公网可信机构所签发的SSL证书的方法

    Windows Server 远程桌面默认使用系统自签名证书,在任何终端进行远程桌面登录时均会提示证书告警 虽然可以通过勾选下方"不再询问我是否连接到此计算机"选项使之后登录不再提 ...

  3. Android和adb命令

    一.名词解释 1.SDK:是软件开发工具包 2.activity(活动):驱使软件运行的一段程序,软件系统和用户进行交互的界面叫一个活动 二.adb命令 1.查看连接的设备:adb devices 2 ...

  4. Spring Boot 中的 ApplicationRunner 和 CommandLineRunner

    前言 一般项目中的初始化操作,初次遇见,妙不可言.如果你还有哪些方式可用于初始化操作,欢迎在评论中分享出来~ ApplicationRunner 和 CommandLineRunner Spring ...

  5. 二进制安装Kubernetes(k8s) v1.22.10 IPv4/IPv6双栈

    二进制安装Kubernetes(k8s) v1.22.10 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes二进制安装 后续尽可能第一时间更 ...

  6. 在Kubernetes(k8s)中使用GPU

    介绍 Kubernetes 支持对节点上的 AMD 和 NVIDIA GPU (图形处理单元)进行管理,目前处于实验状态. 修改docker配置文件 root@hello:~# cat /etc/do ...

  7. Seal AppManager发布:基于平台工程理念的全新应用部署管理体验

    4月12日,数澈软件Seal(以下简称"Seal")宣布推出新一代应用统一部署管理平台 Seal AppManager,采用平台工程的理念,降低基础设施操作的复杂度为研发和运维团队 ...

  8. 随手记:linux校准时间

    记录一下校准时间操作的执行步骤: 首先使用 date 查看当前时间是否准确 校准时间命令 ntpdate cn.pool.ntp.org 如果没有权限: sudo -i 会出现输入密码,直接输入密码即 ...

  9. linux 安装 node 和 npm 服务

    1.安装文件下载 下载地址:https://nodejs.org/zh-cn/download/ 2.安装步骤 1.将安装包上传到指定位置(我习惯放到:/usr/local/application/目 ...

  10. APISIX Ingress 如何使用 Cert Manager 管理证书

    Apache APISIX Ingress Controller 是一款以 Apache APISIX 作为数据面的 Kubernetes Ingress Controller 开源工具,目前已经更新 ...