首页 > 其他分享 >flutter系列之:做一个会飞的菜单

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

时间:2023-06-06 14:00:57浏览次数:36  
标签:动画 菜单 const drawerSlideController 会飞 child return flutter

简介

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

标签:动画,菜单,const,drawerSlideController,会飞,child,return,flutter
From: https://blog.51cto.com/flydean/6424256

相关文章

  • Windows之Visual Studio Code添加右键菜单
    背景通常如果直接从官网下载VisualStudioCode安装包,我们只需要在安装界面勾选那两个复选框就行了,它会安装后帮我们自动添加右键支持,但是,如果我们使用全新的Winget手段安装,因为是静默的,所以没有机会做这个勾选,那么我们只能手动给VisualStudioCode添加右键菜单支持了,让我们通过......
  • 让Flutter 应用程序性能提高 10 倍的 10 个技巧
    Flutter应用程序以其精美的设计和流畅的功能而闻名,但性能问题会很快破坏用户体验。借助这10个优化性能的专家技巧,将您的应用提升到一个新的水平。使用WidgetsBindingObserver跟踪应用程序的生命周期使用“WidgetsBindingObserver”来跟踪您的应用程序的生命周期。此观察器允许......
  • ChatGPT + Flutter快速开发多端聊天机器人App
    ChatGPT+Flutter快速开发多端聊天机器人Appdownload:3wzxit666comChatGPT+Flutter快速开发:打造高效智能的移动应用ChatGPT是一个基于自然语言处理的聊天机器人平台,Flutter则是一个快速、美观、高效的跨平台移动应用开发框架。通过将这两个工具结合起来,可以快速打造出一款高效......
  • React 动态菜单-不限级递归菜单树
    import{FC,useState}from"react";import{Layout,Menu}from'antd';import{Link}from'react-router-dom'import{getData}from"../../mock-data";const{Header,Content,Footer,Sider}=Layout;//菜单数据结构type......
  • Flutter get_storage本地存储
    倪大头关注IP属地:山东0.0972021.09.0818:01:18字数84阅读4,451之前本地存储用的是shared_preferences,但它的存取都是异步的,现在推荐一个Getx提供的本地存储插件get_storagedependencies:get_storage:import'package:get_storage/get_storage.dart';需要......
  • Flutter 使用dio来发起网络请求以及Cookie管理
    前言Flutter官方建议您使用 dio 来发起网络请求,在学习过程中,也尝试过用dartio中的HttpClient发起的请求,这里主要讲一下dio的使用以及CookieJar、CookieManager管理cookie。diodio是一个强大易用的darthttp请求库,支持RestfulAPI、FormData、拦截器、请求取消、Cookie......
  • Flutter依赖注入
    依赖注入依赖注入(DependencyInjection,简称DI)是一种软件设计模式,它的主要目的是将对象之间的依赖关系解耦,使得代码更加可维护、可测试、可扩展,使得代码更易于维护和测试。在Flutter中,DI可以帮助我们管理应用程序中的各种依赖关系,包括服务、数据存储和UI组件等。在DI模式中,我们将......
  • pyqt5笔记-菜单与工具栏
    目录主窗口状态栏动作组合菜单栏子菜单勾选菜单右键菜单工具栏关闭当前窗口和结束应用程序主窗口QMainWindow就是主窗口,QMainWindow提供了更多的特性,如菜单栏、工具栏、状态栏和停靠窗口等,它是为复杂的主窗口应用设计的。QMainWindow继承自QWidget,所以QMainWindow不仅可以......
  • flutter学习笔记(二)
    flutter一切皆widgetflutter和web前端的区别:1.js语法变成dart2.html标签变成组件widget3.flutter里没有css,只有各种widget的属性来实现样式(比如绝对定位用Stack组件来实现)fluter和web前端的相同点:1.dart语法接近js2.flutter里也可以实现flex弹性布局,用Expanded来实现(Expand......
  • 导航和菜单的教程一
    jQuery制作多级导航菜单([color=red][b]说的很详细[/b][/color])[url]http://www.w3cplus.com/jquery/how-to-build-and-enhance-more-level-navigation-menu[/url][color=red][b]先了解css选择器[/b][/color]:[url]http://www.ruanyifeng.com/blog/2009/03......