欢迎来到隐式动画编码实验,您将学习如何使用 Flutter 小部件,轻松地为特定属性集创建动画。

要最大程度地从本编码实验中受益,您应该具备以下基础知识:

本编码实验涵盖以下内容:

  • 使用 AnimatedOpacity 创建淡入效果。
  • 使用 AnimatedContainer 为大小、颜色和边距的过渡添加动画。
  • 隐式动画的概述和使用技术。

完成本编码实验的估计时间:15-30 分钟。

什么是隐式动画?

#

借助 Flutter 的动画库,您可以为 UI 中的小部件添加动感并创建视觉效果。库中的一组小部件可以为您管理动画。这些小部件统称为隐式动画隐式动画小部件,它们的名称来源于它们实现的ImplicitlyAnimatedWidget 类。通过隐式动画,您可以通过设置目标值来为小部件的属性添加动画;每当该目标值发生变化时,小部件就会将该属性从旧值动画到新值。通过这种方式,隐式动画用便利性换取了控制权——它们管理动画效果,而您无需手动操作。

示例:淡入文本效果

#

以下示例展示了如何使用一个名为 AnimatedOpacity 的隐式动画小部件为现有 UI 添加淡入效果。该示例开始时没有动画代码——它包含一个 MaterialApp 主屏幕,其中包含:

  • 一张猫头鹰的照片。
  • 一个“显示详情”按钮,点击后无任何反应。
  • 照片中猫头鹰的描述文本。

淡入(初始代码)

#

要查看示例,请点击“运行”。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

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

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => {},
      ),
      const Column(
        children: [
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      )
    ]);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

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

使用 AnimatedOpacity 小部件进行不透明度动画

#

本节包含一系列步骤,您可以使用它们将隐式动画添加到淡入初始代码中。完成步骤后,您还可以运行淡入完整版代码,其中已包含所做的更改。这些步骤概述了如何使用 AnimatedOpacity 小部件添加以下动画功能:

  • 猫头鹰的描述文本在用户点击“显示详情”之前保持隐藏。
  • 当用户点击“显示详情”时,猫头鹰的描述文本会淡入。

1. 选择要添加动画的小部件属性

#

要创建淡入效果,您可以使用 AnimatedOpacity 小部件为 opacity 属性添加动画。将 Column 小部件包装在 AnimatedOpacity 小部件中。

dart
@override
Widget build(BuildContext context) {
  return ListView(children: <Widget>[
    Image.network(owlUrl),
    TextButton(
      child: const Text(
        'Show Details',
        style: TextStyle(color: Colors.blueAccent),
      ),
      onPressed: () => {},
    ),
    const Column(
      children: [
        Text('Type: Owl'),
        Text('Age: 39'),
        Text('Employment: None'),
      ],
    ),
    AnimatedOpacity(
      child: const Column(
        children: [
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      ),
    ),
  ]);
}

2. 初始化动画属性的状态变量

#

要使用户在点击“显示详情”之前隐藏文本,请将 opacity 的起始值设置为零。

dart
class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0;

  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      // ...
      AnimatedOpacity(
        opacity: opacity,
        child: const Column(

3. 设置动画的时长

#

除了 opacity 参数外,AnimatedOpacity 还需要一个duration 来用于其动画。对于此示例,您可以从 2 秒开始。

dart
AnimatedOpacity(
  duration: const Duration(seconds: 2),
  opacity: opacity,
  child: const Column(

4. 设置动画的触发器并选择结束值

#

配置动画,使其在用户点击“显示详情”时触发。为此,请在 TextButtononPressed() 处理程序中更改 opacity 状态。为了使 FadeInDemo 小部件在用户点击“显示详情”时完全可见,请使用 onPressed() 处理程序将 opacity 设置为 1。

dart
TextButton(
  child: const Text(
    'Show Details',
    style: TextStyle(color: Colors.blueAccent),
  ),
  onPressed: () => {},
  onPressed: () => setState(() {
    opacity = 1;
  }),
),

淡入(完整版)

#

这是您完成更改后的示例。运行此示例,然后点击“显示详情”以触发动画。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

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

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0;

  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => setState(() {
          opacity = 1;
        }),
      ),
      AnimatedOpacity(
        duration: const Duration(seconds: 2),
        opacity: opacity,
        child: const Column(
          children: [
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

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

整合所有概念

#

淡入文本效果”示例演示了 AnimatedOpacity 小部件的以下功能:

  • 它会监听其 opacity 属性的状态变化。
  • opacity 属性发生变化时,它会为 opacity 的新值执行过渡动画。
  • 它需要一个 duration 参数来定义值之间的过渡需要多长时间。

示例:形状变换效果

#

以下示例展示了如何使用 AnimatedContainer 小部件为多个属性(marginborderRadiuscolor)添加具有不同类型(doubleColor)的动画。该示例开始时没有动画代码。它从一个 MaterialApp 主屏幕开始,该屏幕包含:

  • 一个配置了 borderRadiusmargincolorContainer 小部件。这些属性的设置将在您每次运行示例时重新生成。
  • 一个“更改”按钮,点击后无任何反应。

形状变换(初始代码)

#

要开始示例,请点击“运行”。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

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

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: Container(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => {},
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

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

使用 AnimatedContainer 动画颜色、圆角和边距

#

本节包含一系列步骤,您可以使用它们将隐式动画添加到形状变换初始代码中。完成每个步骤后,您还可以运行形状变换完整示例,其中已包含所做的更改。

形状变换初始代码”为 Container 小部件中的每个属性分配一个随机值。关联的函数会生成相关值:

  • randomColor() 函数为 color 属性生成一个 Color
  • randomBorderRadius() 函数为 borderRadius 属性生成一个 double
  • randomMargin() 函数为 margin 属性生成一个 double

以下步骤使用 AnimatedContainer 小部件来:

  • 每当用户点击“更改”时,过渡到 colorborderRadiusmargin 的新值。
  • colorborderRadiusmargin 设置时,为它们的新值执行过渡动画。

1. 添加隐式动画

#

Container 小部件更改为 AnimatedContainer 小部件。

dart
SizedBox(
  width: 128,
  height: 128,
  child: Container(
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
  ),
),

2. 为动画属性设置初始值

#

AnimatedContainer 小部件会在其属性发生变化时在旧值和新值之间进行过渡。为了包含用户点击“更改”时触发的行为,请创建一个 change() 方法。change() 方法可以使用 setState() 方法为 colorborderRadiusmargin 状态变量设置新值。

dart
void change() {
  setState(() {
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  });
}

@override
Widget build(BuildContext context) {
  // ...

3. 设置动画的触发器

#

要将动画设置为在用户每次按下“更改”时触发,请在 onPressed() 处理程序中调用 change() 方法。

dart
ElevatedButton(
  child: const Text('Change'),
  onPressed: () => {},
  onPressed: () => change(),
),

4. 设置时长

#

设置动画的时长,该动画用于驱动旧值和新值之间的过渡。

dart
SizedBox(
  width: 128,
  height: 128,
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
    duration: const Duration(milliseconds: 400),
  ),
),

形状变换(完整版)

#

这是您完成更改后的示例。运行代码并点击“更改”以触发动画。每次点击“更改”时,形状都会动画到其 marginborderRadiuscolor 的新值。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

const _duration = Duration(milliseconds: 400);

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

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

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  void change() {
    setState(() {
      color = randomColor();
      borderRadius = randomBorderRadius();
      margin = randomMargin();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration: _duration,
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => change(),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

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

使用动画曲线

#

前面的示例展示了:

  • 隐式动画允许您为特定小部件属性的值之间的过渡添加动画。
  • duration 参数允许您设置动画完成所需的时间。

隐式动画还允许您控制在设定的 duration 内发生的动画的速率。要定义此速率变化,请将 curve 参数的值设置为一个 Curve,例如在 Curves 类中声明的。

前面的示例没有为 curve 参数指定值。如果没有指定曲线值,隐式动画将应用线性动画曲线

形状变换完整示例中为 curve 参数指定一个值。当您将 easeInOutBack 常量传递给 curve 时,动画会发生变化。

dart
SizedBox(
  width: 128,
  height: 128,
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
    duration: _duration,
    curve: Curves.easeInOutBack,
  ),
),

当您将 Curves.easeInOutBack 常量传递给 AnimatedContainer 小部件的 curve 属性时,请观察 marginborderRadiuscolor 的变化速率如何遵循该常量定义的曲线。

整合所有概念

#

形状变换完整示例”会动画 marginborderRadiuscolor 属性之间的值过渡。AnimatedContainer 小部件会为任何属性的变化添加动画。这些属性包括您未使用的属性,例如 paddingtransform,甚至 childalignment!通过展示隐式动画的其他功能,“形状变换完整示例”在“淡入完整版”示例的基础上进行了扩展。

总结隐式动画:

  • 一些隐式动画,如 AnimatedOpacity 小部件,只为单个属性添加动画。另一些,如 AnimatedContainer 小部件,可以为多个属性添加动画。
  • 隐式动画会在属性值发生变化时,使用提供的 curveduration,为旧值和新值之间的过渡添加动画。
  • 如果您未指定 curve,隐式动画将默认为线性曲线

下一步是什么?

#

恭喜您,您已完成本次编码实验!要了解更多信息,请查看以下建议: