前言
之前一篇我們講了 Flutter組合動(dòng)畫(huà)實(shí)現(xiàn)的方式 —— 交錯(cuò)動(dòng)畫(huà),如需了解原理的可以查看下面這篇:用 Flutter 做出 GIF 圖片的效果 。借助 GIF 和繪圖技巧是可以做到類(lèi)似 GIF 那種效果的。本篇我們來(lái)一個(gè)應(yīng)用實(shí)例,我們讓輪子在草地滾動(dòng)著前進(jìn),而且還能粘上“綠色的草”,運(yùn)行效果如下動(dòng)畫(huà)所示。

動(dòng)畫(huà)解析
上面實(shí)現(xiàn)的效果實(shí)際上有三個(gè)動(dòng)畫(huà)組成:
- 輪子前進(jìn)的動(dòng)畫(huà)
- 輪子滾動(dòng)
- 輪子的邊緣顏色漸變(由黑色變成綠色)
這三個(gè)動(dòng)畫(huà)是同時(shí)進(jìn)行的,因此需要使用到交錯(cuò)動(dòng)畫(huà),即使用一個(gè) AnimationController來(lái)控制三個(gè) Tween 對(duì)象實(shí)現(xiàn)上述的動(dòng)畫(huà)組合。
編碼實(shí)現(xiàn)
首先是輪子組件的定義,為了讓輪子轉(zhuǎn)動(dòng)的效果能夠看到,我們給輪子填充了線性的漸變色,然后輪子的尺寸、旋轉(zhuǎn)速度(time)和邊框顏色由上級(jí)組件來(lái)控制。整個(gè)實(shí)現(xiàn)很簡(jiǎn)單,就是一個(gè)加了裝飾的 Container 而已。
class Wheel extends StatelessWidget {
final double size;
final Color color;
final double time;
const Wheel({
Key? key,
required this.size,
required this.time,
required this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
transform: Matrix4.identity()..rotateZ(2 * pi * time),
transformAlignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: color, width: 10.0),
borderRadius: BorderRadius.circular(size / 2),
gradient: LinearGradient(
colors: [
Colors.white,
Colors.orange[100]!,
Colors.orange[400]!,
],
),
),
);
}
}
然后是整個(gè)頁(yè)面布局,整個(gè)頁(yè)面布局其實(shí)就是一個(gè) Stack,然后底部是綠色的 Container再加兩個(gè)輪子,都是使用 Positioned 來(lái)確定各自的位置。然后就是通過(guò)受控的Tween 對(duì)象控制輪子的旋轉(zhuǎn)速度,輪子外邊沿顏色和移動(dòng)的距離,代碼如下,其中輪子移動(dòng)距離通過(guò)控制邊距實(shí)現(xiàn)。
Widget build(BuildContext context) {
final bottomHeight = MediaQuery.of(context).size.height / 3;
return Scaffold(
appBar: AppBar(
title: const Text('交錯(cuò)動(dòng)畫(huà)'),
),
body: Stack(children: [
Positioned(
child: Container(
width: double.infinity,
height: bottomHeight,
color: Colors.green[400],
),
bottom: 0,
left: 0,
right: 0,
),
Positioned(
child: Wheel(
size: wheelSize,
color: _color.value!,
time: _time.value,
),
left: _offset.value * MediaQuery.of(context).size.width,
bottom: bottomHeight),
Positioned(
child: Wheel(
size: wheelSize,
color: _color.value!,
time: -_time.value,
),
right: _offset.value * MediaQuery.of(context).size.width,
bottom: bottomHeight)
]),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if (_controller.isCompleted) {
_controller.reverse();
} else if (!_controller.isAnimating) {
_controller.forward();
}
},
),
);
}
最后就是構(gòu)建受AnimationController 控制的 Tween 對(duì)象了,這個(gè)在 用 Flutter 做出 GIF 圖片的效果 已經(jīng)介紹過(guò)了,代碼如下:
late AnimationController _controller;
late Animation<double> _time;
late Animation<double> _offset;
late Animation<Color?> _color;
final wheelSize = 80.0;
@override
void initState() {
_controller =
AnimationController(duration: Duration(seconds: 4), vsync: this)
..addListener(() {
setState(() {});
});
_time = Tween<double>(begin: 0, end: 8.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
1.0,
curve: Curves.linear,
),
),
);
_offset = Tween<double>(begin: 0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
1.0,
curve: Curves.easeInCubic,
),
),
);
_color = ColorTween(begin: Colors.black87, end: Colors.green).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.8,
curve: Curves.easeIn,
),
),
);
super.initState();
}
就這樣,一對(duì)奔向?qū)Ψ降妮喿觿?dòng)畫(huà)效果就完成了!源碼已上傳至:動(dòng)畫(huà)相關(guān)源碼。
總結(jié)
交錯(cuò)動(dòng)畫(huà)實(shí)際上可以實(shí)現(xiàn)非常有創(chuàng)意的動(dòng)效,只是這樣會(huì)需要很高的繪圖技巧,比如使用 CustomPaint 來(lái)做。接下來(lái)的幾篇我們來(lái)介紹一下 CustomPaint相關(guān)的內(nèi)容。