首页 > 其他分享 >Flutter 创建一个交错效果的侧边栏菜单

Flutter 创建一个交错效果的侧边栏菜单

时间:2023-12-26 16:00:30浏览次数:27  
标签:动画 菜单 const 侧边 final child return Flutter staggeredController

一、创建一个没有动画效果的菜单

import 'package:flutter/material.dart';

class Menu extends StatefulWidget {
  const Menu({super.key});

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  static const _menuTitles = [
    'Declarative Style',
    'Premade Widgets',
    'Stateful Hot Reload',
    'Native Performance',
    'Great Community',
  ];
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Stack(
        children: [
          _buildFlutterLogo(),
          _buildContent(),
        ],
      ),
    );
  }

  Widget _buildFlutterLogo() {

    return Container();
  }

  Widget _buildContent() {

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

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

  Widget _buildGetStartedButton() {
    return SizedBox(
      width: double.infinity,
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            shape: const StadiumBorder(),
            backgroundColor: Colors.blue,
            padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
          ),
          onPressed: () {},
          child: const Text(
            'Get Started',
            style: TextStyle(
              color: Colors.white,
              fontSize: 22,
            ),
          ),
        ),
      ),
    );
  }
}

二、准备动画

动画计时的控制需要 AnimationController
MenuState添加SingleTickerProviderStateMixin,然后声明并实例化一个AnimationController

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  late AnimationController _staggeredController;

  @override
  void initState() {
    super.initState();

    _staggeredController = AnimationController(
      vsync: this,
    );
  }
}

  @override
  void dispose() {
    _staggeredController.dispose();
    super.dispose();
  }
}

定义动画延迟、单个动画持续时间和总动画持续时间。

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  static const _initialDelayTime = Duration(milliseconds: 50);
  static const _itemSlideTime = Duration(milliseconds: 250);
  static const _staggerTime = Duration(milliseconds: 50);
  static const _buttonDelayTime = Duration(milliseconds: 150);
  static const _buttonTime = Duration(milliseconds: 500);
  final _animationDuration = _initialDelayTime +
      (_staggerTime * _menuTitles.length) +
      _buttonDelayTime +
      _buttonTime;
}

在这种情况下,所有动画延迟50毫秒。之后,列表项开始出现。在前一个列表项开始滑动后,每个列表项的出现延迟50毫秒。每个列表项从右向左滑动需要250毫秒。在最后一个列表项开始滑动后,底部的按钮等待另一个150毫秒弹出。按钮动画耗时500毫秒。

定义每个延迟和动画持续时间后,计算总持续时间,以便可以使用它来计算单个动画时间。

所需的动画时间如下图所示:

若要在较大动画的子部分中对某个值进行动画处理,Flutter提供了Interval类。Interval包含开始时间百分比和结束时间百分比。然后,Interval可以用来在这些开始和结束时间之间动画一个值,而不是使用整个动画的开始和结束时间。例如,给定一个耗时1秒的动画,0.2到0.5的间隔将以200毫秒(20%)开始,500毫秒(50%)结束。

声明并计算每个列表项的间隔和底部按钮的间隔。

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  final List<Interval> _itemSlideIntervals = [];
  late Interval _buttonInterval;

  @override
  void initState() {
    super.initState();

    _createAnimationIntervals();

    _staggeredController = AnimationController(
      vsync: this,
      duration: _animationDuration,
    );
  }

  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,
        ),
      );
    }

    final buttonStartTime =
        Duration(milliseconds: (_menuTitles.length * 50)) + _buttonDelayTime;
    final buttonEndTime = buttonStartTime + _buttonTime;
    _buttonInterval = Interval(
      buttonStartTime.inMilliseconds / _animationDuration.inMilliseconds,
      buttonEndTime.inMilliseconds / _animationDuration.inMilliseconds,
    );
  }
}

三、给列表和按钮添加动画

当菜单变得可见时,交错动画就会播放。
initState()中开始动画.

@override
void initState() {
  super.initState();

  _createAnimationIntervals();

  _staggeredController = AnimationController(
    vsync: this,
    duration: _animationDuration,
  )..forward();
}

每个列表项从右向左滑动并同时淡入。
使用列表项的IntervaleaseOut曲线来动画显示每个列表项的不透明度和平移值。

List<Widget> _buildListItems() {
  final listItems = <Widget>[];
  for (var i = 0; i < _menuTitles.length; ++i) {
    listItems.add(
      AnimatedBuilder(
        animation: _staggeredController,
        builder: (context, child) {
          final animationPercent = Curves.easeOut.transform(
            _itemSlideIntervals[i].transform(_staggeredController.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, vertical: 16),
          child: Text(
            _menuTitles[i],
            textAlign: TextAlign.left,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),
      ),
    );
  }
  return listItems;
}

使用相同的方法来设置底部按钮的不透明度和比例。这一次,使用elasticOut曲线给按钮一个有弹性的效果。

Widget _buildGetStartedButton() {
  return SizedBox(
    width: double.infinity,
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: AnimatedBuilder(
        animation: _staggeredController,
        builder: (context, child) {
          final animationPercent = Curves.elasticOut.transform(
              _buttonInterval.transform(_staggeredController.value));
          final opacity = animationPercent.clamp(0.0, 1.0);
          final scale = (animationPercent * 0.5) + 0.5;

          return Opacity(
            opacity: opacity,
            child: Transform.scale(
              scale: scale,
              child: child,
            ),
          );
        },
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            shape: const StadiumBorder(),
            backgroundColor: Colors.blue,
            padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
          ),
          onPressed: () {},
          child: const Text(
            'Get Started',
            style: TextStyle(
              color: Colors.white,
              fontSize: 22,
            ),
          ),
        ),
      ),
    ),
  );
}

标签:动画,菜单,const,侧边,final,child,return,Flutter,staggeredController
From: https://www.cnblogs.com/angelwgh/p/17928326.html

相关文章

  • 好用小工具推荐:ExplorerPatcher,支持让Win11任务栏不再合并/右键菜单不再繁琐等
    ExplorerPatcher1、软件简介ExplorerPatcher是一款能够帮助我们让win11换回旧版win10任务栏的软件,让我们能够基于以win10上面那么高效的方式来进行生活或者是工作,不少用户或许已经在系统上安装了Windows11系统,win11在许多地方带来了全新的UI界面,但对于新版的任务栏对于很多老Win......
  • Flutter 页面专场动画
    在不同路由(或界面)之间进行切换的时候,许多设计语言,例如Material设计,都定义了一些标准行为。但有时自定义路由会让app看上去更加的独特。为了更好的完成这一点,PageRouteBuilder提供了一个Animation对象。这个Animation能够通过结合Tween以及Curve对象来自定义路由转换......
  • Flutter hero动画
    在Flutter中,图像从当前页面转到另一个页面称为hero动画,相同的动作有时也被称为共享元素过渡。hero动画基本结构在不同页面分别使用两个herowidgets,同时使用配对的标签来实现动画Navigator管理含有app页面的堆栈。推送一个页面或弹出一个Navigator堆栈中的页面会......
  • 私有化JVS低代码平台:多级菜单配置详解
    多级菜单是软件系统一种常见的用户界面设计,它允许用户通过点击或选择不同的菜单项来执行不同的操作或访问不同的功能。多级菜单通常由多个级别的菜单组成,每个级别都包含一组可选择的菜单项。用户可以通过点击或选择菜单项来进入下一级菜单,或者执行相应的操作。那么在JVS低代码平台......
  • 使用 Flutter 制作地图应用
    使用Flutter制作地图应用本文主要介绍使用Flutter制作地图应用在本文中,我将向您展示如何使用Flutter向您的应用程序添加映射功能。对于本教程,您将不需要googlemapsAPI,因此您无需支付任何费用,因为我们将使用另一个免费API,所以不用多说,让我们深入研究它。依赖关系创建一个......
  • 27、flutter Dialog 弹窗
    AlertDialog//放在State<>之下void_alertDialog()async{varresult=awaitshowDialog(barrierDismissible:true,//表示点击灰色背景的时候是否消失弹出框context:context,builder:(context){returnAlertDialog(......
  • flutter响应式
    1LayoutBuilder作用比较类似css的@media,根据不同的尺寸渲染不同的节点LayoutBuilder( builder:(BuildContextcontext,constraints){print(constraints);//BoxConstraints(0.0<=w<=360.0,0.0<=h<=692.0) print(constraints.maxHeight);print(constraints.......
  • Flutter Icons交错动画
    import'package:flutter/material.dart';classAnimateIconsextendsStatelessWidget{constAnimateIcons({super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(�......
  • 26、Flutter中命名路由
    Flutter中的命名路由main.dart中配置路由voidmain(){runApp(MaterialApp(theme:ThemeData(appBarTheme:constAppBarTheme(color:Colors.blue,//设置导航栏颜色(新版本的设置方法)),),//home:Scaffold(body:MyFlutter1())......
  • 25、Flutter中基本路由
    Flutter路由介绍Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过Navigator组件管理路由导航。并提供了管理堆栈的方法。如:Navigator.push和Navigator.popFlutter中给我们提供了两种配置路由跳转的方式:1、基本路由2、命名路由Flutter中的基本路由使用想从HomePage......