📅  最后修改于: 2020-12-08 04:43:03             🧑  作者: Mango
动画是任何移动应用程序中的复杂过程。尽管动画非常复杂,但它可以将用户体验提升到一个新的水平,并提供丰富的用户交互。由于动画的丰富性,动画已成为现代移动应用程序不可或缺的一部分。 Flutter框架认识到动画的重要性,并提供了一个简单直观的框架来开发所有类型的动画。
动画是一种在特定持续时间内以特定顺序显示一系列图像/图片以产生运动幻觉的过程。动画最重要的方面如下-
动画具有两个不同的值:起始值和结束值。动画从“开始”值开始,经过一系列中间值,最后在“结束”值处结束。例如,要使小部件产生动画以淡出,初始值将是完全不透明度,而最终值将是零不透明度。
中间值本质上可以是线性或非线性(曲线),并且可以配置。了解动画按配置工作。每种配置给动画带来不同的感觉。例如,使小部件褪色本质上将是线性的,而球的弹跳本质上将是非线性的。
动画过程的持续时间会影响动画的速度(慢或牢度)。
控制动画过程的能力,例如开始动画,停止动画,重复动画以设置次数,反转动画过程等,
在Flutter中,动画系统不执行任何真实动画。而是仅提供每一帧渲染图像所需的值。
Flutter动画系统基于Animation对象。核心动画类及其用法如下-
在特定持续时间内在两个数字之间生成插值。最常见的动画类是-
Animation
Animation
Animation
AnimationController-特殊的Animation对象,用于控制动画本身。只要应用程序准备好新框架,它就会生成新值。它支持基于线性的动画,其值从0.0到1.0开始
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);
在此,控制器控制动画,持续时间选项控制动画过程的持续时间。 vsync是用于优化动画中使用的资源的特殊选项。
与AnimationController类似,但支持非线性动画。 CurvedAnimation可以与Animation对象一起使用,如下所示-
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
源自Animatable
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this); Animation customTween = IntTween(
begin: 0, end: 255).animate(controller);
Tween也可以与CurvedAnimation一起使用,如下所示-
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation customTween = IntTween(begin: 0, end: 255).animate(curve);
在这里,控制器是实际的动画控制器。曲线提供了非线性类型,customTween提供了从0到255的自定义范围。
动画的工作流程如下-
在StatefulWidget的initState中定义并启动动画控制器。
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween(begin: 0, end: 300).animate(controller);
controller.forward();
添加基于动画的侦听器,addListener更改小部件的状态。
animation = Tween(begin: 0, end: 300).animate(controller) ..addListener(() {
setState(() {
// The state that has changed here is the animation object’s value.
});
});
内置窗口小部件AnimatedWidget和AnimatedBuilder可用于跳过此过程。这两个小部件都接受Animation对象,并获取动画所需的当前值。
在小部件的构建过程中获取动画值,然后将其应用于宽度,高度或任何相关属性,而不是原始值。
child: Container(
height: animation.value,
width: animation.value,
child: ,
)
让我们编写一个基于动画的简单应用程序,以了解Flutter框架中动画的概念。
在Android Studio product_animation_app中创建一个新的Flutter应用程序。
将资产文件夹从product_nav_app复制到product_animation_app,然后在pubspec.yaml文件中添加资产。
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
删除默认的启动代码(main.dart)。
添加导入和基本主要函数。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
创建派生自StatefulWidgtet的MyApp小部件。
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
除了默认的构建方法外,还创建_MyAppState小部件并实现initState并进行配置。
class _MyAppState extends State with SingleTickerProviderStateMixin {
Animation animation;
AnimationController controller;
@override void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10), vsync: this
);
animation = Tween(begin: 0.0, end: 1.0).animate(controller);
controller.forward();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
controller.forward();
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
这里,
在initState方法中,我们创建了一个动画控制器对象(controller),一个动画对象(animation),并使用controller.forward启动了动画。
在部署方法中,我们已经布置了动画控制器对象(controller)。
在构建方法中,通过构造函数将动画发送到MyHomePage小部件。现在,MyHomePage小部件可以使用动画对象为其内容设置动画。
现在,添加ProductBox小部件
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(this.name, style:
TextStyle(fontWeight: FontWeight.bold)),
Text(this.description),
Text("Price: " + this.price.toString()),
],
)
)
)
]
)
)
);
}
}
创建一个新的小部件MyAnimatedWidget,以使用不透明度进行简单的淡入淡出动画。
class MyAnimatedWidget extends StatelessWidget {
MyAnimatedWidget({this.child, this.animation});
final Widget child;
final Animation animation;
Widget build(BuildContext context) => Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) => Container(
child: Opacity(opacity: animation.value, child: child),
),
child: child),
);
}
在这里,我们使用AniatedBuilder制作动画。 AnimatedBuilder是一个小部件,可在同时执行动画的同时构建其内容。它接受动画对象以获取当前动画值。我们使用了animation值animation.value来设置子窗口小部件的不透明度。实际上,小部件将使用不透明度概念为子小部件设置动画。
最后,创建MyHomePage小部件并使用动画对象为其任何内容设置动画。
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title, this.animation}) : super(key: key);
final String title;
final Animation
animation;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: [
FadeTransition(
child: ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"
), opacity: animation
),
MyAnimatedWidget(child: ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"
), animation: animation),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"
),
],
)
);
}
}
在这里,我们使用FadeAnimation和MyAnimationWidget为列表中的前两个项目设置动画。 FadeAnimation是一个内置的动画类,我们使用不透明度概念为其子动画。
完整的代码如下-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State with SingleTickerProviderStateMixin {
Animation animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller);
controller.forward();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
controller.forward();
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title, this.animation}): super(key: key);
final String title;
final Animation animation;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: [
FadeTransition(
child: ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"
),
opacity: animation
),
MyAnimatedWidget(
child: ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"
),
animation: animation
),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"
),
],
)
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image}) :
super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
this.name, style: TextStyle(
fontWeight: FontWeight.bold
)
),
Text(this.description), Text(
"Price: " + this.price.toString()
),
],
)
)
)
]
)
)
);
}
}
class MyAnimatedWidget extends StatelessWidget {
MyAnimatedWidget({this.child, this.animation});
final Widget child;
final Animation animation;
Widget build(BuildContext context) => Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) => Container(
child: Opacity(opacity: animation.value, child: child),
),
child: child
),
);
}
编译并运行该应用程序以查看结果。该应用程序的初始版本和最终版本如下-