1. 內(nèi)置動畫組件
2. 轉(zhuǎn)換動畫(通常命名為xxTransition)
內(nèi)置動畫組件
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 (圖片漸現(xiàn)過渡效果,改變透明度)
AnimatedOpacity({
Key? key,
this.child,
required this.opacity, // 0-1
Curve curve = Curves.linear,
required Duration duration, // 持續(xù)時間
VoidCallback? onEnd, // 動畫結(jié)束后的回調(diào)
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, // 持續(xù)時間
VoidCallback? onEnd, // 動畫結(jié)束后的回調(diào)
});
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 火箭發(fā)射)
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('火箭發(fā)射'),
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(
'發(fā)射',
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(),
),
);
}
}
轉(zhuǎn)換動畫(通常命名為xxTransition)
蘋果風(fēng)格的全屏轉(zhuǎn)換動效
CupertinoFullscreenDialogTransition({
Key? key,
required Animation<double> primaryRouteAnimation,
required Animation<double> secondaryRouteAnimation,
required this.child,
required bool linearTransition,
})
/*
看一下CupertinoFullscreenDialogTransition的build方法:
// 使用了兩個SlideTransition實現(xiàn)該動效。
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,
),
);
}
*/
橫向轉(zhuǎn)換(可實現(xiàn)抽屜效果)
CupertinoPageTransition({
Key? key,
required Animation<double> primaryRouteAnimation,
required Animation<double> secondaryRouteAnimation,
required this.child,
required bool linearTransition,
})
更改 子組件的外框的特性來實現(xiàn)動效
DecoratedBoxTransition
滑動轉(zhuǎn)換
SlideTransition({ // AnimatedWidget的子類
Key? key,
// 使用AnimationController控制,是一個比例偏移。
// new_x = width * dx; new_y = height * dy;
// 如果想讓組件從左邊滑入,可以設(shè)置dx為負值。
required Animation<Offset> position,
this.transformHitTests = true,
this.textDirection,
this.child,
})
旋轉(zhuǎn)轉(zhuǎn)換
RotationTransition({
Key? key,
required Animation<double> turns,
Alignment alignment,
FilterQuality? filterQuality,
Widget? child
})
尺寸轉(zhuǎn)換
SizeTransition({
Key? key,
this.axis = Axis.vertical, // vertical則更改高度;horizontal則更改寬度。
required Animation<double> sizeFactor, // 控制組件尺寸變化的 Animation 對象,乘數(shù)。
this.axisAlignment = 0.0, // 子組件的對齊位置,默認為0.0從中間開始更改尺寸(橫向時可實現(xiàn)卷軸從中間向兩邊打開效果)。當axis為vertical時,-1.0代表頂部對齊開始動畫(即尺寸從上到下開始變大);當 axis 為horizontal 時,開始的方向和文本的反向有關(guān)(TextDirection.ltr 還是 TextDirection.rtl),當文本為從左到右時(TextDirection.ltr,默認),-1.0表示從左側(cè)開始動畫(即尺寸從左到右開始變大)。
this.child,
})
縮放轉(zhuǎn)換
ScaleTransition({
Key? key,
required Animation<double> scale,
this.alignment = Alignment.center,
this.child,
})
更改組件在Stack中的位置
PositionedTransition
漸顯轉(zhuǎn)換
FadeTransition
示例(FadeTransition 漸顯轉(zhuǎn)換)
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 滑動)
// 一張圖片左側(cè)滑入,一張圖片右側(cè)劃出。
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;
// 第一張圖片結(jié)束位置移出右側(cè)屏幕
final end1 = Offset(1.1, 0.0);
// 第二張圖片的初始位置在左側(cè)屏幕
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 組件
對子組件進行轉(zhuǎn)換操作
定義如下:
Transform({
Key? key,
required this.transform, // 一個Matrix4 對象,用于定義三維空間的變換操作。
this.origin, // 一個坐標偏移量,實際會加入到 Matrix4 的 translation(平移)中。
this.alignment, // 轉(zhuǎn)變進行的參考方位
this.transformHitTests = true,
Widget? child, //
}) : assert(transform != null),
super(key: key, child: child);
TweenAnimationBuilder組件(由用戶觸發(fā)動畫)
TweenAnimationBuilder({
Key? key,
required this.tween, // Twee<T>類型,動畫過程中會把 Tween 的中間插值傳給 builder 來構(gòu)建子組件,從而可以實現(xiàn)過渡動畫效果。
required Duration duration,
Curve curve = Curves.linear,
// 構(gòu)建組件。value參數(shù)為tween動畫過程中的中間插值,動畫期間會不斷調(diào)用builder重新繪制子組件。從源碼中可看出初始化時tween起始值和結(jié)束值不一致就會啟動動畫。
// 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
控制組件的陰影、顏色、邊框圓弧等物理模型,但組件自身的形狀不發(fā)生改變
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 屬性實現(xiàn)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
轉(zhuǎn)場時將兩個頁面的元素聯(lián)系使得轉(zhuǎn)場更為平滑。
類似Hero動畫。
2. Shared Axis
共享軸,適用于UI元素之間有空間或引導(dǎo)聯(lián)系的場景,通過在x,y或z軸的轉(zhuǎn)換,實現(xiàn)界面之間的聯(lián)系來進行動畫過渡。
3. Fade Through
通過快速漸現(xiàn)和消失來實現(xiàn)沒有關(guān)聯(lián)UI界面的切換,避免突兀。
4. Fade
彈窗動效。