1. 内置动画组件
2. 转换动画(通常命名为xxTransition)
内置动画组件
1. AnimatedPadding(缩放效果,改变padding)
AnimatedPadding({
Key? key,
required this.padding, // 不允许为负,否则异常
this.child,
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd,
});
2. AnimatedPositioned(配合Stack使用,改变定位)
AnimatedPositioned({
Key? key,
required this.child,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
// 相比Positioned组件多了如下3个属性
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd,
})
3. AnimatedOpacity (图片渐现过渡效果,改变透明度)
AnimatedOpacity({
Key? key,
this.child,
required this.opacity, // 0-1
Curve curve = Curves.linear,
required Duration duration, // 持续时间
VoidCallback? onEnd, // 动画结束后的回调
this.alwaysIncludeSemantics = false, // 默认是 false。用于辅助访问,如果是 true,则不管透明度是多少,都会显示语义信息(可以辅助朗读)。
})
4. AnimatedAlign(改变alignment)
5. AnimatedContainer(改变container属性)
AnimatedContainer({
Key? key,
this.alignment,
this.padding,
Color? color,
Decoration? decoration,
this.foregroundDecoration,
double? width,
double? height,
BoxConstraints? constraints,
this.margin,
this.transform,
this.transformAlignment,
this.child,
this.clipBehavior = Clip.none,
// 相比Container多了如下3个属性
Curve curve = Curves.linear, // 动画曲线,默认是线性
required Duration duration, // 持续时间
VoidCallback? onEnd, // 动画结束后的回调
});
6. AnimatedDefaultTextStyle(改变字体样式)
AnimatedDefaultTextStyle({
Key? key,
required this.child,
required this.style,
this.textAlign,
this.softWrap = true,
this.overflow = TextOverflow.clip,
this.maxLines,
this.textWidthBasis = TextWidthBasis.parent,
this.textHeightBehavior,
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd,
})
7. AnimatedDecoratedBox
示例(AnimatedOpacity图片渐显过渡,一张图逐渐消失另一张图逐渐显示)
class _SwtichImageDemoState extends State<SwtichImageDemo> {
var opacity1 = 1.0;
var opacity2 = 0.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('图片切换'),
brightness: Brightness.dark,
backgroundColor: Colors.black,
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
alignment: Alignment.center,
children: [
AnimatedOpacity(
duration: Duration(milliseconds: 5000),
opacity: opacity1,
child: ClipOval(
child: Image.asset(
'images/beauty.jpeg',
width: 300,
height: 300,
),
),
curve: Curves.ease,
),
AnimatedOpacity(
duration: Duration(milliseconds: 5000),
opacity: opacity2,
child: ClipOval(
child: Image.asset(
'images/beauty2.jpeg',
width: 300,
height: 300,
),
),
curve: Curves.ease,
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Text(
'变',
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
onPressed: () {
setState(() {
opacity1 = 0.0;
opacity2 = 1.0;
});
},
),
);
}
}
示例(AnimatedPositioned 火箭发射)
class RocketLaunch extends StatefulWidget {
RocketLaunch({Key? key}) : super(key: key);
@override
_RocketLaunchState createState() => _RocketLaunchState();
}
class _RocketLaunchState extends State<RocketLaunch> {
var rocketBottom = -80.0;
var rocketWidth = 160.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('火箭发射'),
brightness: Brightness.dark,
backgroundColor: Colors.black,
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Image.asset(
'images/earth.jpeg',
height: double.infinity,
fit: BoxFit.fill,
),
AnimatedPositioned(
child: Image.asset(
'images/rocket.png',
fit: BoxFit.fitWidth,
),
bottom: rocketBottom,
width: rocketWidth,
duration: Duration(seconds: 5),
curve: Curves.easeInCubic,
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Text(
'发射',
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
onPressed: () {
setState(() {
rocketBottom = MediaQuery.of(context).size.height;
rocketWidth = 40.0;
});
},
),
);
}
}
示例(AnimatedPadding、AnimatedPositioned、AnimatedAlign、AnimatedContainer、AnimatedDefaultTextStyle、AnimatedDecoratedBox)
import 'package:flutter/material.dart';
class AnimatedWidgetsTest extends StatefulWidget {
@override
_AnimatedWidgetsTestState createState() => _AnimatedWidgetsTestState();
}
class _AnimatedWidgetsTestState extends State<AnimatedWidgetsTest> {
double _padding = 10;
var _align = Alignment.topRight;
double _height = 100;
double _left = 0;
Color _color = Colors.red;
TextStyle _style = TextStyle(color: Colors.black);
Color _decorationColor = Colors.blue;
@override
Widget build(BuildContext context) {
var duration = Duration(seconds: 5);
return SingleChildScrollView(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
_padding = 20;
});
},
child: AnimatedPadding(
duration: duration,
padding: EdgeInsets.all(_padding),
child: Text("AnimatedPadding"),
),
),
SizedBox(
height: 50,
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: duration,
left: _left,
child: RaisedButton(
onPressed: () {
setState(() {
_left = 100;
});
},
child: Text("AnimatedPositioned"),
),
)
],
),
),
Container(
height: 100,
color: Colors.grey,
child: AnimatedAlign(
duration: duration,
alignment: _align,
child: RaisedButton(
onPressed: () {
setState(() {
_align = Alignment.center;
});
},
child: Text("AnimatedAlign"),
),
),
),
AnimatedContainer(
duration: duration,
height: _height,
color: _color,
child: FlatButton(
onPressed: () {
setState(() {
_height = 150;
_color = Colors.blue;
});
},
child: Text(
"AnimatedContainer",
style: TextStyle(color: Colors.white),
),
),
),
AnimatedDefaultTextStyle(
child: GestureDetector(
child: Text("hello world"),
onTap: () {
setState(() {
_style = TextStyle(
color: Colors.blue,
decorationStyle: TextDecorationStyle.solid,
decorationColor: Colors.blue,
);
});
},
),
style: _style,
duration: duration,
),
AnimatedDecoratedBox(
duration: duration,
decoration: BoxDecoration(color: _decorationColor),
child: FlatButton(
onPressed: () {
setState(() {
_decorationColor = Colors.red;
});
},
child: Text(
"AnimatedDecoratedBox",
style: TextStyle(color: Colors.white),
),
),
)
].map((e) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: e,
);
}).toList(),
),
);
}
}
转换动画(通常命名为xxTransition)
苹果风格的全屏转换动效
CupertinoFullscreenDialogTransition({
Key? key,
required Animation<double> primaryRouteAnimation,
required Animation<double> secondaryRouteAnimation,
required this.child,
required bool linearTransition,
})
/*
看一下CupertinoFullscreenDialogTransition的build方法:
// 使用了两个SlideTransition实现该动效。
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
return SlideTransition(
position: _secondaryPositionAnimation,
textDirection: textDirection,
transformHitTests: false,
child: SlideTransition(
position: _positionAnimation,
child: child,
),
);
}
*/
横向转换(可实现抽屉效果)
CupertinoPageTransition({
Key? key,
required Animation<double> primaryRouteAnimation,
required Animation<double> secondaryRouteAnimation,
required this.child,
required bool linearTransition,
})
更改 子组件的外框的特性来实现动效
DecoratedBoxTransition
滑动转换
SlideTransition({ // AnimatedWidget的子类
Key? key,
// 使用AnimationController控制,是一个比例偏移。
// new_x = width * dx; new_y = height * dy;
// 如果想让组件从左边滑入,可以设置dx为负值。
required Animation<Offset> position,
this.transformHitTests = true,
this.textDirection,
this.child,
})
旋转转换
RotationTransition({
Key? key,
required Animation<double> turns,
Alignment alignment,
FilterQuality? filterQuality,
Widget? child
})
尺寸转换
SizeTransition({
Key? key,
this.axis = Axis.vertical, // vertical则更改高度;horizontal则更改宽度。
required Animation<double> sizeFactor, // 控制组件尺寸变化的 Animation 对象,乘数。
this.axisAlignment = 0.0, // 子组件的对齐位置,默认为0.0从中间开始更改尺寸(横向时可实现卷轴从中间向两边打开效果)。当axis为vertical时,-1.0代表顶部对齐开始动画(即尺寸从上到下开始变大);当 axis 为horizontal 时,开始的方向和文本的反向有关(TextDirection.ltr 还是 TextDirection.rtl),当文本为从左到右时(TextDirection.ltr,默认),-1.0表示从左侧开始动画(即尺寸从左到右开始变大)。
this.child,
})
缩放转换
ScaleTransition({
Key? key,
required Animation<double> scale,
this.alignment = Alignment.center,
this.child,
})
更改组件在Stack中的位置
PositionedTransition
渐显转换
FadeTransition
示例(FadeTransition 渐显转换)
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FadeTransition(
opacity: _animation,
child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()),
),
);
}
示例(PositionedTransition 更改组件在Stack中的位置)
@override
Widget build(BuildContext context) {
const double smallLogo = 100;
const double bigLogo = 200;
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final Size biggest = constraints.biggest;
return Stack(
children: <Widget>[
PositionedTransition(
rect: RelativeRectTween(
begin: RelativeRect.fromSize(
const Rect.fromLTWH(0, 0, smallLogo, smallLogo), biggest),
end: RelativeRect.fromSize(
Rect.fromLTWH(biggest.width - bigLogo,
biggest.height - bigLogo, bigLogo, bigLogo),
biggest),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticInOut,
)),
child: const Padding(
padding: EdgeInsets.all(8), child: FlutterLogo()),
),
],
);
},
);
}
示例(DecoratedBoxTransition 更改外框)
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
final DecorationTween decorationTween = DecorationTween(
begin: BoxDecoration(
color: const Color(0xFFFFFFFF),
border: Border.all(style: BorderStyle.none),
borderRadius: BorderRadius.circular(60.0),
shape: BoxShape.rectangle,
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x66666666),
blurRadius: 10.0,
spreadRadius: 3.0,
offset: Offset(0, 6.0),
)
],
),
end: BoxDecoration(
color: const Color(0xFFFFFFFF),
border: Border.all(
style: BorderStyle.none,
),
borderRadius: BorderRadius.zero,
// No shadow.
),
);
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
)..repeat(reverse: true);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: DecoratedBoxTransition(
position: DecorationPosition.background,
decoration: decorationTween.animate(_controller),
child: Container(
width: 200,
height: 200,
padding: const EdgeInsets.all(10),
child: const FlutterLogo(),
),
),
),
);
}
}
示例(SlideTransition 滑动)
// 一张图片左侧滑入,一张图片右侧划出。
class SlideTransitionDemo extends StatefulWidget {
SlideTransitionDemo({Key? key}) : super(key: key);
@override
_SlideTransitionDemoState createState() => _SlideTransitionDemoState();
}
class _SlideTransitionDemoState extends State<SlideTransitionDemo>
with SingleTickerProviderStateMixin {
bool _forward = true;
final begin = Offset.zero;
// 第一张图片结束位置移出右侧屏幕
final end1 = Offset(1.1, 0.0);
// 第二张图片的初始位置在左侧屏幕
final begin2 = Offset(-1.1, 0.0);
late Tween<Offset> tween1 = Tween(begin: begin, end: end1);
late Tween<Offset> tween2 = Tween(begin: begin2, end: begin);
late AnimationController _controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this);
// 使用自定义曲线动画过渡效果
late Animation<Offset> _animation1 = tween1.animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
late Animation<Offset> _animation2 = tween2.animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SlideTransition'),
brightness: Brightness.dark,
backgroundColor: Colors.black,
),
backgroundColor: Colors.black,
body: Center(
child: Container(
padding: EdgeInsets.all(10.0),
child: Stack(
children: [
SlideTransition(
child: ClipOval(
child: Image.asset('images/beauty.jpeg'),
),
position: _animation1,
),
SlideTransition(
child: ClipOval(
child: Image.asset('images/beauty2.jpeg'),
),
position: _animation2,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.swap_horizontal_circle_sharp),
onPressed: () {
setState(() {
if (_forward) {
_controller.forward();
} else {
_controller.reverse();
}
_forward = !_forward;
});
},
),
);
}
}
示例(SizeTransition)
// 图片从左向右飞入
class SizeTransitionDemo extends StatefulWidget {
SizeTransitionDemo({Key? key}) : super(key: key);
@override
_SizeTransitionDemoState createState() => _SizeTransitionDemoState();
}
class _SizeTransitionDemoState extends State<SizeTransitionDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller =
AnimationController(duration: const Duration(seconds: 3), vsync: this)
..repeat();
late Animation<double> _animation = CurvedAnimation(
parent: _controller, curve: Curves.fastLinearToSlowEaseIn);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SizeTransition'),
brightness: Brightness.dark,
backgroundColor: Colors.blue,
),
body: SizeTransition(
child: Center(
child: Image.asset(
'images/superman.png',
width: 300.0,
height: 300.0,
),
),
sizeFactor: _animation,
axis: Axis.horizontal,
axisAlignment: 1.0,
),
);
}
@override
void dispose() {
_controller.stop();
_controller.dispose();
super.dispose();
}
}
示例(ScaleTransition)
class ScaleTransitionDemo extends StatefulWidget {
ScaleTransitionDemo({Key? key}) : super(key: key);
@override
_ScaleTransitionDemoState createState() => _ScaleTransitionDemoState();
}
class _ScaleTransitionDemoState extends State<ScaleTransitionDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller =
AnimationController(duration: const Duration(seconds: 10), vsync: this)
..repeat();
late Animation<double> _animation =
CurvedAnimation(parent: _controller, curve: Curves.easeOut);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ScaleTransition'),
brightness: Brightness.dark,
backgroundColor: Colors.blue,
),
body: Center(
child: balloon(),
),
);
}
@override
void dispose() {
_controller.stop();
_controller.dispose();
super.dispose();
}
Widget balloon() {
return ScaleTransition(
alignment: Alignment.bottomCenter,
child: Image.asset(
'images/balloon.png',
),
scale: _animation,
);
}
}
其他
Transform 组件
对子组件进行转换操作
定义如下:
Transform({
Key? key,
required this.transform, // 一个Matrix4 对象,用于定义三维空间的变换操作。
this.origin, // 一个坐标偏移量,实际会加入到 Matrix4 的 translation(平移)中。
this.alignment, // 转变进行的参考方位
this.transformHitTests = true,
Widget? child, //
}) : assert(transform != null),
super(key: key, child: child);
TweenAnimationBuilder组件(由用户触发动画)
TweenAnimationBuilder({
Key? key,
required this.tween, // Twee<T>类型,动画过程中会把 Tween 的中间插值传给 builder 来构建子组件,从而可以实现过渡动画效果。
required Duration duration,
Curve curve = Curves.linear,
// 构建组件。value参数为tween动画过程中的中间插值,动画期间会不断调用builder重新绘制子组件。从源码中可看出初始化时tween起始值和结束值不一致就会启动动画。
// typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
required this.builder,
VoidCallback? onEnd,
this.child,
})
示例(滤镜)
class TweenAnimationDemo extends StatefulWidget {
TweenAnimationDemo({Key? key}) : super(key: key);
@override
_TweenAnimationDemoState createState() => _TweenAnimationDemoState();
}
class _TweenAnimationDemoState extends State<TweenAnimationDemo> {
var _sliderValue = 0.0;
Color _newColor = Colors.orange;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TweenAnimationBuilder'),
brightness: Brightness.dark,
backgroundColor: Colors.black,
),
backgroundColor: Colors.black,
body: Center(
child: Column(
children: [
TweenAnimationBuilder(
tween: ColorTween(
begin: Colors.white,
end: _newColor,
),
duration: Duration(seconds: 1),
builder: (_, color, child) {
// 对子组件的每一个像素进行颜色过滤。实际上是插入了一个颜色层,从而看起来有滤镜效果。
return ColorFiltered(
colorFilter:
ColorFilter.mode(color as Color, BlendMode.modulate),
child: ClipOval(
child: ClipOval(
child: Image.asset(
'images/beauty.jpeg',
width: 300,
),
),
),
);
},
),
Slider.adaptive(
value: _sliderValue,
onChanged: (value) {
setState(() {
_sliderValue = value;
});
},
onChangeEnd: (value) {
setState(() {
_newColor = _newColor.withRed((value * 255).toInt());
});
},
)
],
),
),
);
}
}
AnimatedModelBarrier
ModalBarrier 的替换,可以挡住它下层的组件使得这些组件无法与用户交互,并且在组件上加一层颜色动画过渡遮罩。
AnimatedModalBarrier({
Key? key,
required Animation<Color?> color,
bool dismissible, // 为true时点击遮罩会退出当前页返回到上一页
String? semanticsLabel,
bool? barrierSemanticsDismissible
})
AnimatedPhysicalModel
控制组件的阴影、颜色、边框圆弧等物理模型,但组件自身的形状不发生改变
AnimatedPhysicalModel({
Key? key,
required Widget child,
required BoxShape shape,
Clip clipBehavior,
BorderRadius borderRadius,
required double elevation,
required Color color,
bool animateColor,
required Color shadowColor,
bool animateShadowColor,
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd
})
示例
更改elevation 属性实现Z 轴阴影的变化
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AnimatedPhysicalModel 动画'),
),
body: Center(
child: AnimatedPhysicalModel(
child: Container(
width: 300,
height: 300,
),
duration: Duration(seconds: 1),
color: _elevation == 0.0 ? Colors.blue : Colors.green,
animateColor: true,
animateShadowColor: true,
elevation: _elevation,
shape: BoxShape.circle,
shadowColor: Colors.blue[900]!,
curve: Curves.easeInOutCubic,
),
),
floatingActionButton: FloatingActionButton(
child: Text(
'Play',
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
onPressed: () {
setState(() {
_elevation = _elevation == 0 ? 10.0 : 0.0;
});
},
),
);
}
AnimatedSize
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _updateSize(),
child: Container(
color: Colors.amberAccent,
child: AnimatedSize(
curve: Curves.easeIn,
duration: const Duration(seconds: 1),
child: FlutterLogo(size: _size),
),
),
);
}
三方库
- animations 三方库
1. Container Transform
转场时将两个页面的元素联系使得转场更为平滑。
类似Hero动画。
2. Shared Axis
共享轴,适用于UI元素之间有空间或引导联系的场景,通过在x,y或z轴的转换,实现界面之间的联系来进行动画过渡。
3. Fade Through
通过快速渐现和消失来实现没有关联UI界面的切换,避免突兀。
4. Fade
弹窗动效。
网友评论