【Flutter】动画介绍&隐式动画

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Flutter学习
🌠 首发时间:2024年5月28日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

动画基本原理

在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。

我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下,对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32FPS,大多数人基本上就感受不到差别了。

由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。

Flutter动画简介

FLutter 中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画和 Hero 动画。

隐式动画

通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,FLutter 中提供的 AnimatedContainerAnimatedPaddingAnimatedPositioned
AnimatedOpacityAnimatedDefaultTextStyleAnimatedSwitcher 都属于隐式动画。

隐式动画中可以通过 duration 来配置动画时长,通过 curve (曲线)来配置动画过程,这两个属性在上述组件中都是存在的。

curve

Flutter 中,Curves 类提供了一系列预定义的曲线,用于控制动画的速度变化。以下是一些常用的 Curves 组件的值及简单解释:

  1. Curves.linear

    • 线性曲线,动画以恒定的速度进行,没有加速或减速。
  2. Curves.decelerate

    • 减速曲线,动画开始时速度较快,然后逐渐减速。
  3. Curves.ease

    • 标准的加速减速曲线,动画开始和结束时速度较慢,中间时速度较快。
  4. Curves.easeIn

    • 加速曲线,动画开始时速度较慢,然后逐渐加速。
  5. Curves.easeOut

    • 减速曲线,动画开始时速度较快,然后逐渐减速。
  6. Curves.easeInOut

    • 加速减速曲线,动画开始和结束时速度较慢,中间时速度较快,类似于Curves.ease
  7. Curves.fastOutSlowIn

    • 快出慢入曲线,动画开始时速度较快,然后逐渐减速到结束。
  8. Curves.bounceIn

    • 弹簧效果曲线,动画开始时速度为0,然后加速进入动画,到达最大速度后反弹一次。
  9. Curves.elasticIn

    • 弹性效果曲线,动画开始时速度为0,然后加速进入动画,到达最大速度后会有一些超过目标值的回弹效果。

这些是常用的一些Curves组件的值,通过选择合适的曲线,可以控制动画的速度变化,从而实现不同的动画效果。

AnimatedContainer

AnimatedContainer 的属性和 Container 属性基本是一样的,不同的地方是,当 AnimatedContainer 属性改变的时候会触发动画。

下面的程序中,我们通过浮动按钮来改变 AnimatedContainertransform 的值,以触发动画。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;
  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedContainer'),
      ),
      body: Center(
        child: AnimatedContainer(
          duration: const Duration(seconds: 1, milliseconds: 100),
          width: 200,
          height: 200,
          transform: flag
              ? Matrix4.translationValues(0, 0, 0)
              : Matrix4.translationValues(-100, 0, 0),
          color: Colors.blue,
        ),
      ),
    );
  }
}

AnimatedPadding

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedPadding'),
      ),
      body: AnimatedPadding(
        curve: Curves.linear,
        padding: EdgeInsets.fromLTRB(10, flag ? 10 : 200, 0, 0),
        duration: const Duration(seconds: 2),
        child: Container(
          width: 100,
          height: 100,
          color: Colors.blue,
        ),
      ),
    );
  }
}

AnimatedPositioned

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedPositioned'),
      ),
      body: Stack(
        children: [
          ListView(
            children: const [
              ListTile(
                title: Text("我是一个列表"),
              ),
              ListTile(
                title: Text("我是一个列表"),
              ),
              ListTile(
                title: Text("我是一个列表"),
              ),
              ListTile(
                title: Text("我是一个列表"),
              ),
              ListTile(
                title: Text("我是一个列表"),
              ),
              ListTile(
                title: Text("我是一个列表"),
              ),
            ],
          ),
          AnimatedPositioned(
              curve: Curves.linear,
              right: flag ? 10 : 300,
              top: flag ? 10 : 560,
              duration: const Duration(seconds: 1, milliseconds: 500),
              child: Container(
                width: 60,
                height: 60,
                color: Colors.blue,
              )),
        ],
      ),
    );
  }
}

AnimatedOpacity

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedOpacity'),
      ),
      body: Center(
        child: AnimatedOpacity(
          opacity: flag ? 0.2 : 1,
          duration: const Duration(seconds: 1),
          curve: Curves.easeIn,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.red,
          ),
        ),
      ),
    );
  }
}

AnimatedDefaultTextStyle

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedDefaultTextStyle'),
      ),
      body: Center(
        child: Container(
          alignment: Alignment.center,
          width: 300,
          height: 300,
          color: Colors.blue,
          child: AnimatedDefaultTextStyle(
            duration: const Duration(seconds: 1),
            style: TextStyle(fontSize: flag ? 20 : 50, color: Colors.white),
            child: const Text("你好Flutter"),
          ),
        ),
      ),
    );
  }
}

AnimatedSwitcher

Flutter 中的 AnimatedSwitcher 是一个用于在切换子元素时执行动画效果的预置组件。它允许你在切换子元素时使用自定义的过渡效果,从而实现平滑的切换体验。

AnimatedSwitcher 通常用于在切换应用程序界面的不同部分或内容时提供动画效果。例如,当用户点击按钮切换页面或者切换视图时,可以使用 AnimatedSwitcher 来实现页面间的过渡动画。

在使用 AnimatedSwitcher 时,你需要提供新旧子元素,并指定一个过渡效果。当新的子元素被提供时,AnimatedSwitcher 将会在旧的子元素和新的子元素之间执行过渡动画。

要使用 AnimatedSwitcher,通常需要指定以下几个关键属性:

  • child:当前的子元素,即将要显示的子元素。
  • duration:动画的持续时间,用于控制过渡动画的速度。
  • transitionBuilder:一个回调函数,用于定义子元素切换时的过渡效果。该回调函数接受当前子元素和动画对象,并返回一个 Widget,用于实现过渡效果。

通过这些属性,你可以轻松地在 Flutter 应用程序中添加动画效果,从而提升用户体验并增加应用的交互性。AnimatedSwitcherFlutter 框架中 Material 组件库的一部分,因此可以与其他 Material 风格的组件无缝集成。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedSwitcher'),
      ),
      body: Center(
        child: Container(
            alignment: Alignment.center,
            width: 300,
            height: 220,
            color: Colors.orange,
            child: AnimatedSwitcher(
                //当子元素改变的时候会触发动画
                duration: const Duration(seconds: 1),
                child: flag
                    ? const CircularProgressIndicator()
                    : Image.network(
                        "https://xixi-web-tlias.oss-cn-guangzhou.aliyuncs.com/5.jpg",
                        fit: BoxFit.cover,
                      ))),
      ),
    );
  }
}

Flutter 中的 CircularProgressIndicator 是一个用于显示循环进度的预置组件。它通常用于在应用程序加载数据、执行长时间任务或执行任何需要显示进度的操作时显示一个圆形的进度条。

CircularProgressIndicator 可以以不同的方式进行定制,包括更改颜色、大小和动画。它是 Flutter 框架中 Material 组件库的一部分,因此在使用 MaterialAppScaffoldMaterial 风格的组件时很常见。

你可以通过简单的代码来创建一个 CircularProgressIndicator,然后将其放置在应用程序的合适位置。在加载数据或执行任务时,CircularProgressIndicator 将显示一个圆形进度条,直到任务完成为止。

修改 AnimatedSwitcher 的动画效果

通过 AnimatedSwitcher 中的 transitionBuilder 参数可以修改其动画效果。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedSwitcher'),
      ),
      body: Center(
        child: Container(
            alignment: Alignment.center,
            width: 300,
            height: 220,
            color: Colors.orange,
            child: AnimatedSwitcher(
                transitionBuilder: ((child, animation) {
                  //可以改变动画效果
                  return ScaleTransition(
                    scale: animation,
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                }),
                duration: const Duration(seconds: 1),
                //当子元素改变的时候会触发动画
                child: flag
                    ? const CircularProgressIndicator()
                    : Image.network(
                        "https://xixi-web-tlias.oss-cn-guangzhou.aliyuncs.com/5.jpg",
                        fit: BoxFit.cover,
                      ))),
      ),
    );
  }
}

在上面的代码中,transitionBuilder 参数是用于定义 AnimatedSwitcher 在切换子元素时所使用的过渡效果的回调函数。这个回调函数接受两个参数:

  1. child:当前子元素(即将要切换的元素)。
  2. animation:表示当前切换的动画。在这里,animation 是一个 Animation<double> 类型的对象,它描述了从0到1的动画值。

transitionBuilder 回调函数应该返回一个 Widget,这个 Widget 将作为子元素在切换时的动画效果。在上面的代码中,回调函数使用了两种过渡效果:

  1. ScaleTransition:它通过对子元素进行缩放来实现动画效果,缩放的比例由动画值 animation 控制,从而实现子元素的逐渐放大或缩小的效果。
  2. FadeTransition:它通过改变子元素的不透明度来实现淡入淡出的效果,不透明度的变化由动画值 animation 控制,从而实现子元素的逐渐显现或消失的效果。

通过结合使用 ScaleTransitionFadeTransition,可以实现更加丰富的过渡效果,使切换子元素时更加平滑和动态。

子组件相同的AnimatedSwitcher

我们知道,AnimatedSwitcher 只有在其子元素发生改变时才会触发动画,那么当子元素不变时,比如子元素为一个 Text 组件,我们只修改里面的内容,不改变组件类型,在这种情况下又该如何触发动画呢?

很简单,我们只要设置一下子元素的 key 值为 UniqueKey(),就可以让 AnimatedSwitcher 认为子元素已经改变。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
      ),
      appBar: AppBar(
        title: const Text('AnimatedSwitcher'),
      ),
      body: Center(
          child: Container(
        alignment: Alignment.center,
        width: 300,
        height: 220,
        color: Colors.blue,
        child: AnimatedSwitcher(
            transitionBuilder: ((child, animation) {
              //可以改变动画效果
              return ScaleTransition(
                scale: animation,
                child: FadeTransition(
                  opacity: animation,
                  child: child,
                ),
              );
            }),
            duration: const Duration(seconds: 1),
            //当子元素改变的时候会触发动画
            child: Text(
              key: UniqueKey(),
              flag ? "凡所过往" : "皆为序章",
              style: Theme.of(context).textTheme.headlineLarge,
            )),
      )),
    );
  }
}
  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序喵正在路上

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值