Flutter 的动画包【Flutter 专题 4】

介绍 Flutter 的新动画包

Flutter 团队最近发布了其出色的跨平台移动框架的新稳定版本。这个新版本包括许多新的升级,包括改进的移动性能、减小的应用程序大小、iOS 设备上的 Metal 支持、新的 Material 小部件等等。


在这些新功能中,真正引起我注意的是新的动画包。基于谷歌的新Material Design规范,这个包允许开发人员在移动应用程序开发中实现动画模式。


根据文档,“该软件包包含用于通常所需效果的预制动画。动画可以根据您的内容进行定制,并将其放入您的应用程序中以取悦您的用户。”


在本文中,我将讨论新动画包中的内容以及如何在您的应用中使用它来创建更漂亮的 UI 交互。了解 Flutter 和 Dart 的基本知识应该足以阅读本文——说了这么多,让我们开始吧!

Material Design 的运动系统是什么?

根据 Material Design 网站的说法,“运动系统是一组可以帮助用户理解和导航应用程序的过渡模式。” 基本上,Material 的运动规范由允许有意义和美观的 UI 交互的通用过渡模式组成。


在撰写本文时,Material 运动包/库可用于原生 Android 开发和 Flutter 开发。在 Flutter 中,这以动画包的形式出现。


包中目前有四种过渡模式可用:


  1. 容器转换
  2. 共享轴过渡
  3. 通过过渡淡入淡出
  4. 淡入淡出


我们现在将看看如何使用 Flutter 和动画包来实现这些过渡模式。

设置一个新的 Flutter 项目

首先,您必须创建一个新的 Flutter 应用程序。我通常使用 VSCode Flutter 扩展来做到这一点。创建 Flutter 项目后,将动画包作为依赖项添加到文件中:pubspec.yaml


dependencies:
  flutter:
    sdk: flutter
  animations: ^1.1.2

复制代码


现在运行以下命令以获取所需的包:


flutter pub get

复制代码


设置好新的 Flutter 应用程序后,让我们开始编写一些代码。

容器改造

根据 Material 运动规范,“容器转换模式是为包含容器的 UI 元素之间的转换而设计的。这种模式在两个 UI 元素之间创建了可见的连接。” 容器在整个过渡期间充当持久元素。


您可以在动画包文档中观看容器转换的一些示例。如您所见,在过渡期间,有一个公共元素:容器,它保存传出和传入元素,并且其尺寸和位置会发生变化。


为了实现容器转换,我们可以使用OpenContaineranimations 包提供的小部件。OpenContainer允许我们定义容器关闭时的内容(初始内容)和打开时容器的内容。我们还可以定义其他属性,例如颜色和容器在关闭和打开状态下的高度。


实现容器转换的代码如下所示:


import 'package:animations/animations.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: TestingContainer(),
    ),
  );
}

class TestingContainer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
      floatingActionButton: OpenContainer(
          closedBuilder: (_, openContainer) {
            return FloatingActionButton(
              elevation: 0.0,
              onPressed: openContainer,
              backgroundColor: Colors.blue,
              child: Icon(Icons.add, color: Colors.white),
            );
          },
          openColor: Colors.blue,
          closedElevation: 5.0,
          closedShape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(100)),
          closedColor: Colors.blue,
          openBuilder: (_, closeContainer) {
            return Scaffold(
              appBar: AppBar(
                backgroundColor: Colors.blue,
                title: Text("Details"),
                leading: IconButton(
                  onPressed: closeContainer,
                  icon: Icon(Icons.arrow_back, color: Colors.white),
                ),
              ),
              body: (ListView.builder(
                  itemCount: 10,
                  itemBuilder: (_, index) {
                    return ListTile(
                      title: Text(index.toString()),
                    );
                  })),
            );
          }),
    );
  }
}

复制代码


如您所见,我们OpenContainer有两个命名参数(以及其他参数),称为closedBuilderopenBuilder。这两个参数都采用一个返回小部件的函数。


该函数接受一个类型的对象BuildContext和一个打开容器(在 的情况下closedBuilder)或关闭容器(在 的情况下openBuilder)的函数。返回的 widgetclosedBuilder是容器关闭状态下的内容,返回的 widgetopenBuilder是打开状态下的内容。结果应该是:


Flutter 的动画包【Flutter 专题 4】

共享轴过渡模式

根据文档,“共享轴模式用于具有空间或导航关系的 UI 元素之间的转换。这种模式在 x、y 或 z 轴上使用共享变换来加强元素之间的关系。” 因此,如果您需要沿特定轴为导航设置动画,则共享轴过渡模式适合您。


您可以通过观看包文档页面上的动画来更好地理解我的意思。对于共享轴过渡模式的实现,动画包为我们提供了PageTransitionSwitcherSharedAxisTransition小部件。


PageTransitionSwitcher当它的孩子改变时,小部件只是从一个旧的孩子过渡到一个新的孩子。你应该总是给每个孩子PageTransitionSwitcher一个唯一的键,这样 Flutter 就知道小部件现在有一个新的孩子。这可以很容易地用一个UniqueKey对象来完成。


除了 child 参数之外,PageTransitionSwitcher还有其他命名参数:duration,用于设置过渡的持续时间;reverse,它接受一个布尔值并决定过渡是否应该“向后播放”;和transitionBuilder,它接受一个返回小部件的函数。


在我们的例子中,我们将返回一个SharedAxisTransition小部件。在SharedAxisTransition小部件中,我们可以设置transitionType(是否要沿 x 轴、y 轴或 z 轴过渡)。我们还有animationsecondaryAnimation参数,它们分别定义了驱动孩子进入和退出的动画以及驱动新孩子在旧孩子之上过渡的动画。


实现的代码SharedAxisTransition如下所示:


import 'package:animations/animations.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: TestingSharedAxis(),
    ),
  );
}

class TestingSharedAxis extends StatefulWidget {
  @override
  _TestingSharedAxisState createState() => _TestingSharedAxisState();
}

class _TestingSharedAxisState extends State<TestingSharedAxis> {
  bool _onFirstPage = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: SafeArea(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  FlatButton(
                      onPressed: _onFirstPage == true
                          ? null
                          : () {
                              setState(() {
                                _onFirstPage = true;
                              });
                            },
                      child: Text(
                        "First Page",
                        style: TextStyle(
                            color: _onFirstPage == true
                                ? Colors.blue.withOpacity(0.5)
                                : Colors.blue),
                      )),
                  FlatButton(
                      onPressed: _onFirstPage == false
                          ? null
                          : () {
                              setState(() {
                                _onFirstPage = false;
                              });
                            },
                      child: Text(
                        "Second Page",
                        style: TextStyle(
                            color: _onFirstPage == false
                                ? Colors.red.withOpacity(0.5)
                                : Colors.red),
                      ))
                ],
              ),
            ),
            Expanded(
              child: PageTransitionSwitcher(
                duration: const Duration(milliseconds: 300),
                reverse: !_onFirstPage,
                transitionBuilder: (Widget child, Animation<double> animation,
                    Animation<double> secondaryAnimation) {
                  return SharedAxisTransition(
                    child: child,
                    animation: animation,
                    secondaryAnimation: secondaryAnimation,
                    transitionType: SharedAxisTransitionType.horizontal,
                  );
                },
                child: _onFirstPage
                    ? Container(
                        key: UniqueKey(),
                        color: Colors.blue,
                        child: Align(
                          alignment: Alignment.topCenter,
                          child: Text("FIRST PAGE"),
                        ),
                      )
                    : Container(
                        key: UniqueKey(),
                        color: Colors.red,
                        child: Align(
                          alignment: Alignment.topCenter,
                          child: Text("SECOND PAGE"),
                        ),
                      ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

复制代码


在上面的代码块中,我们定义了一个名为 的私有布尔变量_onFirstPage,如果我们在第一页,则为真,否则为假。我们还使用 的值_onFirstPage来定义 的反向参数的值PageTransitionSwitcher。这允许PageTransitionSwitcher在切换回第一页时“弹出”第二页。


结果应该是这样的:


Flutter 的动画包【Flutter 专题 4】

淡入淡出过渡模式

淡入淡出过渡模式用于在彼此不强相关的 UI 元素之间进行过渡。查看文档页面以了解此过渡模式的外观。


淡入淡出过渡模式的实现与共享轴过渡模式的实现非常相似。在这里,FadeThroughTransition使用 代替SharedAxisTransition。下面是使用动画包在 Flutter 中简单实现淡入淡出模式的代码:


import 'package:animations/animations.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: TestingFadeThrough(),
    ),
  );
}

class TestingFadeThrough extends StatefulWidget {
  @override
  _TestingFadeThroughState createState() => _TestingFadeThroughState();
}

class _TestingFadeThroughState extends State<TestingFadeThrough> {
  int pageIndex = 0;
  List<Widget> pageList = <Widget>[
    Container(key: UniqueKey(), color: Colors.red),
    Container(key: UniqueKey(), color: Colors.blue),
    Container(key: UniqueKey(), color: Colors.green)
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Testing Fade Through')),
      body: PageTransitionSwitcher(
        transitionBuilder: (Widget child, Animation<double> animation,
            Animation<double> secondaryAnimation) {
          return FadeThroughTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            child: child,
          );
        },
        child: pageList[pageIndex],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: pageIndex,
        onTap: (int newValue) {
          setState(() {
            pageIndex = newValue;
          });
        },
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.looks_one),
            label: "First page",
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.looks_two), label: 'Second Page'),
          BottomNavigationBarItem(
            icon: Icon(Icons.looks_3),
            label: 'Third Page',
          ),
        ],
      ),
    );
  }
}

复制代码


我们在这里所做的是非常基本的;我们正在根据BottomNavigationBarItem当前选择的索引呈现一个新的孩子。请注意,每个孩子都有一个唯一的键。就像我之前说的,这让 Flutter 能够区分不同的孩子。结果应该是这样的:


Flutter 的动画包【Flutter 专题 4】

淡入淡出过渡模式

当元素需要在屏幕的转入(进入)或转出(退出)时使用此转换模式,例如在模式或对话框的情况下。


为了在 Flutter 中实现这一点,我们将不得不使用FadeScaleTransitionAnimationController来控制过渡的孩子的入口和出口。我们将利用我们的AnimationController状态来确定是显示子小部件还是隐藏它。


下面是淡入淡出过渡的代码实现:


import 'package:animations/animations.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: TestingFadeScale(),
    ),
  );
}

class TestingFadeScale extends StatefulWidget {
  @override
  _TestingFadeScaleState createState() => _TestingFadeScaleState();
}

class _TestingFadeScaleState extends State<TestingFadeScale>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  @override
  void initState() {
    _controller = AnimationController(
        value: 0.0,
        duration: const Duration(milliseconds: 500),
        reverseDuration: const Duration(milliseconds: 250),
        vsync: this)
      ..addStatusListener((status) {
        setState(() {});
      });
    super.initState();
  }

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

  bool get _isAnimationRunningForwardsOrComplete {
    switch (_controller.status) {
      case AnimationStatus.forward:
      case AnimationStatus.completed:
        return true;
      case AnimationStatus.reverse:
      case AnimationStatus.dismissed:
        return false;
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Testing FadeScale Transition'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  onPressed: () {
                    if (_isAnimationRunningForwardsOrComplete) {
                      _controller.reverse();
                    } else {
                      _controller.forward();
                    }
                  },
                  color: Colors.blue,
                  child: Text(_isAnimationRunningForwardsOrComplete
                      ? 'Hide Box'
                      : 'Show Box'),
                )
              ],
            ),
          ),
          AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return FadeScaleTransition(animation: _controller, child: child);
            },
            child: Container(
              height: 200,
              width: 200,
              color: Colors.blue,
            ),
          ),
        ],
      ),
    );
  }
}



如您所见,FadeScaleTransition小部件有一个名为 的命名参数animation,它接受一个AnimationController. 结果应如下所示:


Flutter 的动画包【Flutter 专题 4】

showModal功能

动画包还带有一个名为 的适当命名的函数showModal,它(顾名思义)用于显示模态。


showModal接受各种参数,其中一些参数包括:context,用于定位模式的导航器;builder,这是一个返回模态内容的函数;和configuration


configuration参数接收一个扩展ModalConfiguration类的 widget,用于定义 modal 的属性,例如 barrier 的颜色(屏幕上没有被 modal 覆盖的部分)、duration、进入和退出过渡,以及很快。


下面是showModal函数在代码中的样子:


import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() {
  runApp(
    MaterialApp(
      home: TestingShowModal(),
    ),
  );
}

class TestingShowModal extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    timeDilation = 20;
    return Scaffold(
      body: Center(
        child: RaisedButton(
            color: Colors.blue,
            child: Text(
              "Show Modal",
              style: TextStyle(color: Colors.white),
            ),
            onPressed: () {
              showModal(
                  context: context,
                  configuration: FadeScaleTransitionConfiguration(),
                  builder: (context) {
                    return AlertDialog(
                      title: Text("Modal title"),
                      content: Text("This is the modal content"),
                    );
                  });
            }),
      ),
    );
  }
}

复制代码


在上面的代码块中,我们使用了FadeScaleTransitionConfiguration作为我们的配置参数。的FadeScaleTransitionConfiguration是,延伸的预定义类ModalConfiguration,用来淡入淡出过渡的属性添加到我们的模式。

覆盖默认页面路由转换

随着SharedAxisPageTransitionsBuilderFadeThroughPageTransitionsBuilderpageTransitionsTheme我们的参数MaterialApp的主题,我们可以覆盖,当我们在我们的颤振应用从一个路径切换到另一个时出现的默认切换动画。


要做到这一点SharedAxisPageTransitionsBuilder


void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        pageTransitionsTheme: const PageTransitionsTheme(
          builders: <TargetPlatform, PageTransitionsBuilder>{
            TargetPlatform.android: SharedAxisPageTransitionsBuilder(
                transitionType: SharedAxisTransitionType.horizontal),
          },
        ),
      ),
      home: HomePage(),
    ),
  );
}

复制代码


要做到这一点FadeThroughPageTransitionsBuilder


void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        pageTransitionsTheme: const PageTransitionsTheme(
          builders: <TargetPlatform, PageTransitionsBuilder>{
            TargetPlatform.android: FadeThroughPageTransitionsBuilder()
          },
        ),
      ),
      home: HomePage(),
    ),
  );
}

复制代码

结论

正如我向您展示的,动画包非常适合向您的 Flutter 应用程序添加有用的 UI 交互和过渡。


好的,今天的分享到这儿就和大家说再见了,感谢大家的观看

上一篇:内附PPT下载 | 阿里云资深技术专家 陈长城:一站式数据管理DMS及最新解决方案解读


下一篇:一脸懵逼加从入门到绝望学习hadoop之Caused by: java.net.UnknownHostException: master报错