简介

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

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. Kafka 生产者写入数据

    一.生产者发送消息的步骤

  2. 超详细!新手如何创建一个Vue项目

    目录 一.在官网下载Vue.js 二.使用<script>标签直接引入本地的vue.js 三.使用CDN引入Vue.js 四.验证是否安装成功 五.安装Vue Devtools浏览器调试插 ...

  3. 中心极限定理的模拟—R实现

    中心极限定理,是指概率论中讨论随机变量序列部分和分布渐近于正态分布的一类定理.这组定理是数理统计学和误差分析的理论基础,指出了大量随机变量近似服从正态分布的条件.它是概率论中最重要的一类定理,有广泛的 ...

  4. SpringIOC注入

    在lagou的训练营的学习历程 SpringIOC实例化Bean的三种方式:1.使用无参构造器2.静态方法3.实例化方法.他要先实例化创建类(和2的区别),再调用. XML注入属性DI依赖注入,根据实 ...

  5. Python最详细的Excel操作方式,

    为 什么要学会使用Excel在机器学习模型的建立中,不得不借助于Excel强大的统计分析能力,这个时候就牵涉到了如何将python直接处理得到的数据存入excel表格当中以及获取表格的数据进行分析,简 ...

  6. StampedLock:JDK1.8中新增,比ReadWriteLock还快的锁

    摘要:StampedLock是一种在读取共享变量的过程中,允许后面的一个线程获取写锁对共享变量进行写操作,使用乐观读避免数据不一致的问题,并且在读多写少的高并发环境下,比ReadWriteLock更快 ...

  7. [Linux/CENTOS]YUM提示: Another app is currently holding the yum lock; waiting for it to exit...

    1 问题描述 使用yum安装Nginx的安装依赖组件: yum -y install gcc gcc-c++ automake autoconf libtool make 但是,在执行过程中出现如下信 ...

  8. Python程序笔记20230302

    Alice.Bob 和他们的朋友们 问题主体 密码学家 Rivest.Shamir.Adleman 于1977年4月撰写了一篇论文<数字签名与公钥密码学>(On Digital Signa ...

  9. Mybatis-Plus如何自定义SQL注入器?

    有关Mybatis-Plus常用功能之前有做过一篇总结: MyBatisPlus常用功能总结!(附项目示例) 一.什么是SQL注入器 我们在使用Mybatis-Plus时,dao层都会去继承BaseM ...

  10. Yii初学者必看-yii 表单验证规则

    对yii深入了解总结出:希望对初学者有些帮助 Active Record (AR) 是一个流行的 对象-关系映射 (ORM) 技术. 每个 AR 类代表一个数据表(或视图),数据表(或视图)的列在 A ...