在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由 于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。
我们将UI 的一次改变称为一个动画帧,对应一次屏幕刷新,屏幕刷新率,而决定动画流畅度的一个重要指标就是帧率FPS ( Frame Per Second),即每秒的动画帧数,体现为每秒能生产图片的多少。
很明显,帧率越高则动画就会越流畅!一般情况下, 对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32 FPS,大多数人基本上就感受不到差别了。
由于动画的每一帧都是要改变UI输出,所以在一个时间段内 连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧 率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。
隐式动画
显式动画
自定义隐式动画、自定义显式动画
Hero 动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以 叫隐式动画, Flutter中提供的 AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。
隐式动画中可以通过 duration配置动画时长、可以通过 Curve (曲线)来配置动画过程
AnimatedContainer的属性和Container属性基本是一样的,当AnimatedContainer属性改变的时候就会触发动画。
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.animation),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text("AnimatedContainer Demo"),),body: Center(child: AnimatedContainer(duration: const Duration(milliseconds: 500),// 动画时长500 ms width: flag?100:300,height: flag ? 100 : 300,color: Colors.blue,),),);}
}
linear 匀速的
decelerate 匀减速
ease 开始加速,后面减速
easeIn 开始慢,后面快
easeOut 开始快,后面慢
easeInOut 开始慢,然后加速,最后再减速
更多曲线 https://docs.flutter.io/flutter/animation/Curves-class.html
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.animation),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text("AnimatedContainer Demo"),),body: AnimatedPadding(duration: const Duration(milliseconds: 2000),// 动画时长500 ms curve: Curves.bounceInOut,padding: EdgeInsets.fromLTRB(10, flag ? 10 : 500, 0, 0),child: Container(width: 100,height: 100,color: Colors.red,),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@overrideMyHomePageState createState() => MyHomePageState();
}class MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),body: Stack(children: [AnimatedPositioned(curve: Curves.easeInOut,duration: const Duration(seconds: 1),top: flag ? 10 : 500,left: flag ? 10 : 300,child: Container(width: 60,height: 60,color: Colors.blue,),),Align(alignment: const Alignment(0, 0.8),child: ElevatedButton(child: const Text("Transform"),onPressed: () {setState(() {flag = !flag;});},),),],),);}
}
import 'dart:isolate';
import 'dart:math';import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@overrideMyHomePageState createState() => MyHomePageState();
}class MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(onPressed: (){setState(() {flag= !flag;});},child: const Icon(Icons.opacity),),appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),body: Center(child: AnimatedOpacity(curve: Curves.linear,duration: const Duration(seconds: 1),opacity: flag?1:0,child: Container(width: 300,height: 300,color: Colors.blue,),),),);}
}
import 'dart:isolate';
import 'dart:math';import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(onPressed: () {setState(() {flag = !flag;});},child: const Icon(Icons.opacity),),appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),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,),child: const Text("你好Flutte"),),),),);}
}
AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、AnimatedDefaultTextStyle都是在属性改变的时候执行动画, AnimatedSwitcher则是在子元素改变的 时候执行动画。
相比上面的动画组件AnimatedSwitcher多了transitionBuilder参数,可以在transitionBuilder中自定义 动画
示例1:载页面前显示一个loading动画,加载完毕后显示对于的内容
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(onPressed: () {setState(() {flag = !flag;});},child: const Icon(Icons.opacity),),appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 180,color: Colors.yellow,child: AnimatedSwitcher(duration: const Duration(milliseconds: 1000),child: flag? const CircularProgressIndicator(): Image.asset("images/2.png",fit: BoxFit.contain,),),),),);}
}
示例2:通过transitionBuilder自定义动画效果
import 'dart:isolate';
import 'dart:math';import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(onPressed: () {setState(() {flag = !flag;});},child: const Icon(Icons.opacity),),appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 180,color: Colors.yellow,child: AnimatedSwitcher(transitionBuilder: ((child, animation) {return ScaleTransition(scale: animation,child: FadeTransition(opacity: animation,child: child,),);}),duration: const Duration(milliseconds: 400),child: flag? const CircularProgressIndicator(): Image.asset("images/2.png",fit: BoxFit.contain,),),),),);}
}
示例3:通过transitionBuilder改变子元素执行动画
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({Key? key}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(onPressed: () {setState(() {flag = !flag;});},child: const Icon(Icons.opacity),),appBar: AppBar(title: const Text("AnimatedPositioned Demo"),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 180,color: Colors.yellow,child: AnimatedSwitcher(transitionBuilder: ((child, animation) {return ScaleTransition(scale: animation,child: FadeTransition(opacity: animation,child:child ,),);}),duration: const Duration(milliseconds: 400),child: Text(key:UniqueKey(),flag?"你好Flutter":"你好大地",style: const TextStyle(fontSize: 30),),),),),);}
}
常见的显式动画有RotationTransition、FadeTransition、ScaleTransition、SlideTransition、AnimatedIcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController 控制动画的开始、暂停、重置、跳转、倒播等
示例1:AnimationController普通用法
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => MyHomePageState();
}class MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();// TODO: implement initState_controller = AnimationController(// Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,// 只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保// 持同步,以达到供需平衡的效果,防止卡顿现象。vsync:this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [RotationTransition(turns: _controller,child: const FlutterLogo(size: 100,),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Wrap(spacing: 10,alignment: WrapAlignment.center,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次}, child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次}, child: const Text("Reverse")),ElevatedButton(onPressed: () {_controller.stop(); //停止播放}, child: const Text("Stop")),ElevatedButton(onPressed: () {_controller.reset(); //重置}, child: const Text("rest")),ElevatedButton(onPressed: () {_controller.repeat(); //重复播放}, child: const Text("repeat"))],),)],),);}
}
示例2: lowerBound upperBound
AnimationController 用于控制动画,它包含动画的启动 forward() 、停止 stop() 、反向播放 reverse() 等方法。 AnimationController 会在动画的每一帧,就会生成一个新的值。默认情况 下, AnimationController 在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字 ,我们也 可以通过 lowerbound 和 upperBound 来修改 AnimationController 生成数字的区间。
代码
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {
// TODO: implement initState_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),lowerBound: 3, //第三圈转到第五圈upperBound: 5);_controller.addListener(() {print(_controller.value);});}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [RotationTransition(turns: _controller,child: const FlutterLogo(size: 100,),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Wrap(spacing: 10,alignment: WrapAlignment.center,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),ElevatedButton(onPressed: () {_controller.stop(); //停止播放},child: const Text("Stop")),ElevatedButton(onPressed: () {_controller.reset(); //重置},child: const Text("rest")),ElevatedButton(onPressed: () {_controller.repeat(); //重复播放},child: const Text("repeat"))],),)],),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [FadeTransition(opacity: _controller,child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {
// TODO: implement initState_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [ScaleTransition(scale: _controller,child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
说明
默认情况下, AnimationController对象值的范围是[0.0 , 1.0]。如果我们需要构建UI的动画值在不同 的范围或不同的数据类型,则可以使用 Tween来添加映射以生成不同的范围或数据类型的值
代码
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {// TODO: implement initState_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [ScaleTransition(scale: _controller.drive(Tween(begin: 1, end: 2)),child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
这是一负责平移的显示动画组件,使用时需要通过position属性传入一个Animated表示位移程度,通常 借助Tween实现
示例1:_controller.drive驱动动画
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [SlideTransition(position: _controller.drive(Tween(begin: const Offset(0, 0),end: const Offset(1.2, 0),),),child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),),],),);}
}
示例2:Tween.animate 驱动动画
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [SlideTransition(position: Tween(begin: const Offset(0, 0),end: const Offset(1.2, 0) //表示实际的位置向右移动自身宽度的1.2倍).animate(_controller),child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
示例3: 链式操作修改动画效果
Curve:动画曲线,作用和Android中的Interpolator(差值器)类似,负责控制动画变化的速率,通俗地讲就是使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。
Tween:将 AnimationController 生成的 [0,1]值映射成其他属性的值,比如颜色、样式等。
https://www.jianshu.com/p/ef1b6212bcb8
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {
// TODO: implement initState_controller = AnimationController(vsync: this,duration: const Duration(seconds: 1),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [SlideTransition(position:Tween(begin: const Offset(0, -1), end: const Offset(0, 0.8)).chain(CurveTween(curve: Curves.bounceIn)).animate(_controller),child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
示例4: 链式操作修改动动画执行时间
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {
// TODO: implement initState_controller = AnimationController(vsync: this,duration: const Duration(seconds: 3),);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [SlideTransition(position:Tween(begin: const Offset(0, -1), end: const Offset(0, 0.8)).chain(CurveTween(curve: Curves.bounceIn)).chain(CurveTween(curve: const Interval(0.8, 1.0))).animate(_controller),child: const FlutterLogo(size: 80),),const SizedBox(height: 40,),Padding(padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {_controller.forward(); //正序播放一次},child: const Text("Forward")),ElevatedButton(onPressed: () {_controller.reverse(); //倒序播放一次},child: const Text("Reverse")),],),)],),);}
}
AnimatedIcon顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以Animated开头,但是他 是一个显式动画组件,需要通过progress属性传入动画控制器,另外需要由Icon属性传入动画图标数据
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller =AnimationController(vsync: this, duration: const Duration(seconds: 1));}@overridevoid dispose() {super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.add),onPressed: () {_controller.forward();},),appBar: AppBar(title: const Text('Title'),),body: Center(child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller, size: 40),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;bool flag = true;@overridevoid initState() {super.initState();_controller = AnimationController(vsync: this,duration: const Duration(seconds: 6),)..repeat(reverse: true);}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {flag ? _controller.forward() : _controller.reverse();flag = !flag;},),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [SlidingBox(controller: _controller,color: Colors.blue[200],curve: const Interval(0, 0.2),),SlidingBox(controller: _controller,color: Colors.blue[400],curve: const Interval(0.2, 0.4),),SlidingBox(controller: _controller,color: Colors.blue[600],curve: const Interval(0.4, 0.6),),SlidingBox(controller: _controller,color: Colors.blue[800],curve: const Interval(0.6, 0.8),),SlidingBox(controller: _controller,color: Colors.blue[900],curve: const Interval(0.8, 1.0),),],),),);}
}class SlidingBox extends StatelessWidget {final AnimationController controller;final Color? color;final Curve curve;const SlidingBox({super.key,required this.controller,required this.color,required this.curve});@overrideWidget build(BuildContext context) {return SlideTransition(position: Tween(begin: const Offset(0, 0), end: const Offset(0.3, 0)).chain(CurveTween(curve: Curves.bounceInOut)).chain(CurveTween(curve: curve)).animate(controller),child: Container(width: 220,height: 60,color: color,),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),body: Center(child: TweenAnimationBuilder(tween: Tween(begin: 100.0, end: flag ? 100.0 : 200.0),duration: const Duration(seconds: 1),builder: ((context, value, child) {return Icon(Icons.star,size: value,);})),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends State {bool flag = true;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),body: Center(child: TweenAnimationBuilder(tween: Tween(begin: 0.0, end: flag ? 0.0 : 1.0),duration: const Duration(seconds: 1),builder: ((context, value, child) {return Opacity(opacity: value,child: Container(color: Colors.red,width: 200,height: 200,),);})),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller =AnimationController(vsync: this, duration: const Duration(seconds: 1))..repeat(reverse: true);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Center(child: AnimatedBuilder(animation: _controller,builder: (BuildContext context, Widget? child) {return Opacity(opacity: _controller.value, //从0到1的变化child: Container(width: 200,height: 200,color: Colors.red,child: const Text("我是一个text组件"),),);},),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller =AnimationController(vsync: this, duration: const Duration(seconds: 1))..repeat(reverse: true);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Center(child: AnimatedBuilder(animation: _controller,builder: (BuildContext context, Widget? child) {return Opacity(opacity: Tween(begin: 0.5, end: 1.0).animate(_controller).value, //从0.5到1的变化child: Container(width: 200,height: 200,color: Colors.red,child: const Text("我是一个text组件"),),);},),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller =AnimationController(vsync: this, duration: const Duration(seconds: 1))..repeat(reverse: true);}@overrideWidget build(BuildContext context) {Animation x = Tween(begin: -100.0, end: 100.0).chain(CurveTween(curve: Curves.easeIn)).chain(CurveTween(curve: const Interval(0.2, 0.8))).animate(_controller);return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Center(child: AnimatedBuilder(animation: _controller,builder: (BuildContext context, Widget? child) {return Container(width: 200,height: 200,color: Colors.red,transform: Matrix4.translationValues(x.value, 0, 0),child: const Text("我是一个text组件"),);},),),);}
}
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@overrideState createState() => _MyHomePageState();
}class _MyHomePageState extends Statewith SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();_controller =AnimationController(vsync: this, duration: const Duration(seconds: 1))..repeat(reverse: true);}@overrideWidget build(BuildContext context) {Animation x = Tween(begin: -100.0, end: 100.0).chain(CurveTween(curve: Curves.easeIn)).chain(CurveTween(curve: const Interval(0.2, 0.4))).animate(_controller);return Scaffold(appBar: AppBar(title: const Text('Title'),),body: Center(child: AnimatedBuilder(animation: _controller,builder: (BuildContext context, Widget? child) {return Container(width: 200,height: 200,color: Colors.red,transform: Matrix4.translationValues(x.value, 0, 0),child: child,);},child: const Text("我是一个text组件"),),),);}
}
微信朋友圈点击小图片的时候会有一个动画效果到大图预览,这个动画效果就可以使用Hero 动画实 现。
Hero 指的是可以在路由(页面)之间“飞行”的 widget,简单来说 Hero 动画就是在路由切换时,有一个共 享的widget 可以在新旧路由间切换。
import 'package:flutter/material.dart';
import '../../res/listData.dart';
import 'hero.dart';
import 'hero2.dart';class HomePage extends StatefulWidget {const HomePage({super.key});@overrideState createState() => _HomePageState();
}class _HomePageState extends State {List _getListData() {var tempList = listData2.map((value) {return GestureDetector(onTap: () {Navigator.push(context, MaterialPageRoute(builder: (setting) {// return HeroPage(arguments: {// "imageUrl": value['imageUrl'],// "description": value['title'],// });return HeroPage2(arguments: {"imageUrl": value['imageUrl'],"description": value['title'],});}));},child: Container(decoration: BoxDecoration(border: Border.all(color: const Color.fromRGBO(233, 233, 233, 0.9), width: 1)),child: Column(children: [Hero(tag: value['title'],// child: Image.asset(value['imageUrl'])),child: Image.network(value['imageUrl'],width: 320,height: 120,fit: BoxFit.cover,),),const SizedBox(height: 12),Text(value['title'],textAlign: TextAlign.center,style: const TextStyle(fontSize: 20),)],)),);});return tempList.toList();}@overrideWidget build(BuildContext context) {return GridView.count(crossAxisSpacing: 10.0,//水平子 Widget 之间间距mainAxisSpacing: 10.0,//垂直子 Widget 之间间距padding: const EdgeInsets.all(10),crossAxisCount: 2,//一行的 Widget 数量// childAspectRatio:0.7,// 宽度和高度的比例children: _getListData(),);}
}import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';class HeroPage2 extends StatefulWidget {final Map arguments;const HeroPage2({super.key, required this.arguments});@overrideState createState() => _HeroPageState();
}class _HeroPageState extends State {@overrideWidget build(BuildContext context) {return GestureDetector(onTap: () {Navigator.pop(context);},child: Hero(tag: widget.arguments["imageUrl"],child: Scaffold(backgroundColor: Colors.black,body: Center(child: AspectRatio(aspectRatio: 16 / 9,child: PhotoView(imageProvider: NetworkImage(widget.arguments["imageUrl"]),),),),),),);}
}
import 'package:flutter/scheduler.dart';
void initState() { super.initState(); timeDilation=5.0; }
1、配置依赖
photo_view: ^0.14.0
2、引入
import 'package:photo_view/photo_view.dart';
3、单张图片的预览
@override
Widget build(BuildContext context) {return Container(child: PhotoView(imageProvider: AssetImage("assets/large-image.jpg"),));
}
4、多张图片的预览
引入
import 'package:photo_view/photo_view_gallery.dart'
示例
PhotoViewGallery.builder(itemCount: 5,builder: ((context, index) {return PhotoViewGalleryPageOptions(imageProvider: NetworkImage(listData[index]["imageUrl"]));
}))PhotoViewGallery.builder(scrollPhysics: const BouncingScrollPhysics(),builder: (BuildContext context, int index) {return PhotoViewGalleryPageOptions(imageProvider:NetworkImage(widget.imageItems[index]["imageUrl"]),);},scrollDirection: widget.direction,itemCount: widget.imageItems.length,backgroundDecoration:const BoxDecoration(color: Colors.black),pageController: PageController(initialPage: 1),onPageChanged: (index) => setState(() {currentIndex = index;
}))
(1)scrollPhysics
BouncingScrollPhysics() 滑动到边界的时候有弹跳的效果
(2)scrollDirection
Axis.horizontal 水平 、Axis.vertical垂直方向
(3)backgroundDecoration
背景颜色
(4)builder
builder函数 根据配置的itemCount渲染函数
(5)itemCount
数量
(6)pageController
PageController(initialPage: 1)
(7)onPageChanged
onPageChanged触发的方法