经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Flutter » 查看文章
flutter系列之:做一个会飞的菜单
来源:cnblogs  作者:flydean  时间:2023/6/7 10:54:14  对本文有异议

简介

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

原文链接:https://www.cnblogs.com/flydean/p/17460344.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号