1
头图

在动画领域,Flutter 提供了一系列功能,包括基于物理的动画,可以模拟真实世界的动态,在应用程序中创建更逼真和自然的运动。

本文将深入研究 Flutter 动画,探索各种类型,并演示如何在项目中实现它们。

Flutter 的动画系统围绕着一个“Animation 对象”的概念,这个对象的值会随着时间而变化,这个变化由一个“AnimationController”来控制,它定义了动画的持续时间、方向和其他参数,要设置一个动画,这两个元素必须连接起来。

常用的动画类

以下是在 Flutter 中创建动画时经常使用的一些关键类:

  • Tween:定义动画在其间进行插值的值范围。例如,它可以指定动画的开始值和结束值。
  • AnimationController:这个类控制动画进程,允许你启动、停止和重启动画,以及配置动画的持续时间和曲线。
  • AnimatedBuilder:这个小部件在动画发生变化时会重新构建自身,对于制作包含多个小部件的复杂动画特别有用。
  • Curve:Curve(曲线)决定了动画的进度,Flutter 提供了内置的曲线,如 LinearProgressIndicatorCurves.easeInOut ,或者你可以设计自己的自定义曲线。

基于物理的动画

Flutter 提供了 Simulation 类,用于创建具有初始状态和演化规则的基于物理的模拟,这个类使你能够制作各种基于物理的动画,包括基于弹簧动力学、摩擦力和重力的动画。

让我们考虑 Flutter 中基于物理的动画的示例:弹簧动画。 SpringSimulation 类可用于创建此动画,模拟阻尼谐振子。以下是如何使用 SpringSimulation 生成类似弹簧的动画:

// Import required packages
import 'package:flutter/material.dart';

// 定义一个 SpringAnimation 小部件
class SpringAnimation extends StatefulWidget {
  const SpringAnimation({Key? key});
  @override
  _SpringAnimationState createState() => _SpringAnimationState();
}
class _SpringAnimationState extends State<SpringAnimation> with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 3),
  )..forward();
  late final Animation<double> _animation = Tween<double>(
    begin: 0,
    end: 400,
  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Curves.elasticOut,
  ));
  void _startAnimation() {
    _controller.reset();
    _controller.forward();
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              return Stack(
                children: [
                  Positioned(
                    left: 70,
                    top: _animation.value,
                    child: child!,
                  )
                ],
              );
            },
            child: GestureDetector(
              onTap: () {
                _startAnimation();
              },
              child: Container(
                height: 100,
                width: 250,
                decoration: BoxDecoration(
                  color: const Color(0xFF00EF3C),
                  borderRadius: BorderRadius.circular(10),
                ),
                child: const Center(
                    child: Text(
                  'SEMAPHORE',
                  style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
                )),
              ),
            ),
          ),
        ),
      ),
    );
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

在这个例子中, SpringAnimation 小部件使用 AnimationController 来驱动 Animation 对象, Tween 定义了动画的值范围, CurvedAnimationCurves.elasticOut 赋予了动画一个弹性缓动曲线, AnimatedBuilder 小部件用于根据动画的值来动画 Container 小部件的位置, Positioned 小部件确保 Container 小部件在屏幕上的位置与动画的值相关,当小部件被构建时, AnimationController 启动了 Animation 对象的值动画,从而推动了 Container 小部件的位置动画。

结果是一个迷人的弹簧般的动画,在屏幕上产生弹跳效果。从本质上讲,基于物理的动画为您提供了一种强大的机制,可以在您的应用程序中制作逼真且流畅的动作,而 Flutter 提供了一系列工具和类来促进其实现。

Hero 动画

Flutter 中的 Hero 小部件充当不同屏幕之间共享元素转换的管道。例如,Hero 小部件可以以动画方式将小部件从一个屏幕过渡到另一个屏幕,包含图像、文本甚至容器等多种元素。该小部件通过为共享元素设置动画来促进无缝过渡。

Hero 小部件的一个关键方面是要求在起始和目标屏幕上都具有相同的标签,这个标签对于识别正在进行转换的共享元素至关重要。

考虑下面的示例,它演示了如何利用 Hero 小部件实现图像缩略图和全屏视图之间的平滑过渡。

缩略图屏幕

import 'package:flutter/material.dart';

class ThumbnailScreen extends StatelessWidget {
  const ThumbnailScreen({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Thumbnail Screen'),
      ),
      body: Column(
        children: [
          GridView.count(
            crossAxisSpacing: 15,
            crossAxisCount: 2,
            children: [
              Hero(
                tag: 'image1',
                child: GestureDetector(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => const FullScreenScreen(
                           imageAsset: 'assets/white_puma.jpg',
                          heroTag: 'image1',
                        ),
                      ),
                    );
                  },
                  child: Image.asset('assets/white_puma.jpg'),
                ),
              ),
              Hero(
                tag: 'image2',
                child: GestureDetector(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => const FullScreenScreen(
                          imageAsset: 'assets/red_nike.jpg',
                          heroTag: 'image2',
                        ),
                      ),
                    );
                  },
                  child: Image.asset('assets/red_nike.jpg',
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

屏幕全屏

class FullScreenScreen extends StatelessWidget {
  final String imageAsset;
  final String heroTag;
  const FullScreenScreen({super.key, required this.imageAsset, required this.heroTag});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Hero(
          tag: heroTag,
          child: Image.asset(imageAsset),
        ),
      ),
    );
  }
}

ThumbnailScreen 中,Hero 小部件封装了 Image 小部件,每个小部件都有一个独特的标签来区分共享元素。当用户与图像交互时,导航操作将他们引导到 FullScreenScreen

FullScreenScreen 中,图像被另一个 Hero 小部件包围,它与 ThumbnailScreen 中对应的图像具有相同的标签。这个标签控制着图像从缩略图屏幕到全屏显示的动画。此外,图像嵌套在一个GestureDetector中,使用户可以点击屏幕上的任何地方以恢复到缩略图屏幕。

当用户点击 ThumbnailScreen 上的图像时,Flutter 会编排一个动画,将共享元素传输到 FullScreenScreen 上。当用户点击 FullScreenScreen 上的图像时,Flutter 会执行一个动画,将共享元素返回到 ThumbnailScreen 上。Hero 小部件在渲染屏幕之间的无缝和迷人的过渡方面被证明是无价的,它对于增强电子商务应用程序的用户体验尤其有效。

隐式的动画

隐式动画是在 Flutter 中生成简单动画的重要工具,可响应小部件属性的变化。与深入研究复杂的动画控制器和微调器相比,隐式动画使您能够对小部件的属性制作动画,而无需关心动画的复杂细节。AnimatedContainer 小部件就是隐式动画工具的一个典型例子。

AnimatedContainer 小部件类似于标准的容器,但它拥有额外的动画功能,它可以动画地改变属性,如大小、颜色和形状,让我们通过一个示例来演示如何使用 AnimatedContainer 小部件在按钮被按下时动画地改变颜色。

隐式动画示例

// Import required packages and libraries
import 'package:flutter/material.dart';

// Define the ImplicitAnimations widget
class ImplicitAnimations extends StatefulWidget {
  const ImplicitAnimations({Key? key}) : super(key: key);
  @override
  createState() => ImplicitAnimationsState();
}
class ImplicitAnimationsState extends State<ImplicitAnimations> {
  bool _isPressed = false;
  void _togglePressed() {
    setState(() {
      _isPressed = !_isPressed;
    });
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Color and Position Change'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              AnimatedContainer(
                duration: const Duration(seconds: 1),
                height: 200.0,
                width: 200.0,
                margin: EdgeInsets.only(
                  top: _isPressed ? 70.0 : 0.0,
                ),
                decoration: BoxDecoration(
                  color: _isPressed ? Colors.blue : Colors.red,
                  borderRadius: BorderRadius.circular(_isPressed ? 50.0 : 0.0),
                ),
              ),
              const SizedBox(height: 20.0),
              ElevatedButton(
                onPressed: _togglePressed,
                child: const Text('ANIMATE'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

在本例中,ImplicitAnimations 部件包含一个 _isPressed 布尔变量和一个 _togglePressed() 函数,用于切换布尔变量的值。该部件的状态类在 _togglePressed() 中调用了 setState(),以便在值发生变化时触发部件树的重建。

这个小部件的结构围绕着一个位于 Scaffold 中的 Center 小部件,这个 Center 小部件容纳了一个 Column ,它包含两个主要元素:

  1. AnimatedContainer。该小部件根据 _isPressed 值为其属性设置动画。在此示例中,高度、宽度、边距、颜色和边框半径的更改均以动画形式呈现。
  2. ElevatedButton。该按钮按下时会激活动画,调用 _togglePressed() 函数来切换 _isPressed

隐式动画是一种用户友好的方法,可以将动画引入 Flutter 应用程序,而无需复杂的动画控制器和渐变。

动画矢量图形

Flutter 通过不同的工具支持矢量动画图形,这进一步体现了 Flutter 制作复杂动态动画的能力。基于矢量的图形在调整大小后仍能保持质量。Flutter 利用不同的工具来创建动画并将其导入应用程序。

Rive

Rive 提供了一种制作跨平台动画的方法,从移动到 Web。Rive 动画可以导出为 Rive 文件,可以通过 Rive 包集成到您的 Flutter 项目中。该包还提供了一个 Rive 小部件来展示应用程序中的 Rive 动画。

让我们深入研究在 Flutter 应用中实现 Rive 的过程:

// Import required packages and libraries
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

// Define the RiveAnimations widget
class RiveAnimations extends StatelessWidget {
  const RiveAnimations({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey,
      body: Center(
        child: Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20),
            color: Colors.blue,
          ),
          width: 300,
          height: 300,
          child: const RiveAnimation.asset('assets/ball.riv'),
        ),
      ),
    );
  }
}

要使用 Rive 在 Flutter 动画中加入动画矢量图形,请按照以下步骤操作:

  1. 使用 rive 网站创建动画并以适当的格式下载。
  2. Rive 包导入到你的 Flutter 项目中。
  3. 利用 RiveAnimation.asset 小部件,提供 Rive 文件的路径。

动画可以使用 RiveAnimationController 类进行控制,允许您根据需要启动、停止和暂停动画。

上面的动画是用 rive 制作的,它演示了一个球被弹起和踢出,但它并没有描绘 rive 的所有属性。Rive 可以用来为你的动画设置不同的状态,这让你的动画更动态。你可以让动画对应用程序的状态做出反应,但你必须擅长动画,才能利用 rive 的这一方面。除了 Rive,你还可以探索其他工具,如 Lottie,将预先构建的动画集成到 Flutter 应用程序中。

Lottie 动画

Lottie 是一个流行的库,它允许你使用 Adobe After Effects 等动画工具生成的 JSON 文件在应用中渲染动画。Lottie 动画是基于矢量的,提供流畅和高质量的视觉体验。在 Flutter 中,您可以使用 Lottie 包集成 Lottie 动画,它提供了 Lottie.assetLottie.network 小部件。

下面是一个如何在 Flutter 中创建 Lottie 动画的示例:

// Import required packages and libraries
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';

// Define the LottieAnimations widget
class LottieAnimations extends StatelessWidget {
  const LottieAnimations({Key? key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const SizedBox(height: 50),
            SizedBox(
              height: 200,
              width: 200,
              child: LottieBuilder.asset('assets/shopping.json'),
            ),
            const SizedBox(height: 50),
            const Text(
              'add items to cart',
              style: TextStyle(fontSize: 30),
            ),
          ],
        ),
      ),
    );
  }
}

Lottie 动画轻松让您的应用程序栩栩如生。只需找到或创建 Lottie 格式 (JSON) 所需的动画,导入 Lottie 包,然后使用提供的小部件在 Flutter 应用程序中展示您的动画。

结论

动画在移动应用程序开发中发挥着关键作用,通过为应用程序注入活力和参与度来增强用户体验。本文深入探讨了 Flutter 的动画功能,从动画系统和基本类到基于物理的动画、自定义动画和使用 Hero 小部件的动画过渡等高级技术。我们还讨论了隐式动画以及使用 Rive 和 Lottie 等包集成动画矢量图形。

通过利用这些资源,你可以深入研究 Flutter 动画的世界,制作迷人的动态用户界面,使你的移动应用程序与众不同。


杭州程序员张张
11.8k 声望6.7k 粉丝

Web/Flutter/独立开发者/铲屎官