image
用到的知識點(diǎn)
- Matrix4矩陣
- 貝塞爾曲線
第一步:畫出目標(biāo)運(yùn)行大致軌跡路線
首先我們先畫一條二階貝塞爾曲線,這樣我們能更直觀的觀察到目標(biāo)移動的大致軌跡。我們先確定二階貝塞爾曲線的三個點(diǎn):p0(開始點(diǎn))、p1(控制點(diǎn))、p2(結(jié)束點(diǎn)),像上圖的紅線軌跡,屏幕的中心點(diǎn)開始,到達(dá)屏幕的寬2/3的位置,那么p0和p2的點(diǎn)就可以確定了:
// 二階貝塞爾曲線 p0:開始點(diǎn)、p1:控制點(diǎn)、p2:結(jié)束點(diǎn)
Offset p0, p1, p2;
@override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
if (p0 == null) {
p0 = Offset(_size.width/2, _size.height/2);
p1 = Offset(_size.width+_size.width/4, _size.height/4);
p2 = Offset(_size.width*2/3, 0);
}
}
復(fù)制代碼
得到二階貝塞爾曲線的三個點(diǎn)之后就可以用 CustomPaint 把它畫出來了,因?yàn)槊看?setState 之后,build 會重新走一遍,所以我加了判斷讓它們只初始化一次就夠了。
第二步:放置紙飛機(jī)在屏幕的中心點(diǎn)
Flutter里面使用矩陣是通過 Transform 這個 Widget 來設(shè)置的,Transform 有多個擴(kuò)展構(gòu)造方法,比如 Transform.rotate、Transform.translate、Transform.scale,有興趣的可以自己去了解一下,在這里我們使用 Transform 默認(rèn)的構(gòu)造方法才能通過 Matrix4 來實(shí)現(xiàn)飛機(jī)的移動、縮放、旋轉(zhuǎn)。
// 矩陣
Matrix4 _matrix4 = Matrix4.identity();
// 飛機(jī)尺寸
Size planeSize;
@override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
if (planeSize == null) {
planeSize = Size(_size.width/4, _size.width/4);
}
return Stack(
children: [
Container(
alignment: Alignment.center,
child: Transform(
transform: _matrix4,
child: SvgPicture.asset(Res.svg_paper_plane, width: planeSize.width, height: planeSize.height,),
),
),
],
);
}
復(fù)制代碼
第三步:設(shè)置動畫,通過動畫計(jì)算飛機(jī)移動軌跡等
這一步最重要,涉及到移動軌跡、旋轉(zhuǎn)角度和縮放倍數(shù)的計(jì)算,這里面涉及到一些計(jì)算,我直接把代碼貼出來(PS:畢竟我數(shù)學(xué)太過垃圾,這一步花了我好多時間也沒能找到移動距離、縮放倍數(shù)、旋轉(zhuǎn)角度的最佳計(jì)算公式,只能自己一步一步慢慢調(diào)到大致的視覺效果)。
// 移動軌跡點(diǎn),即移動物的中心點(diǎn)
Offset bezierCenter;
// 當(dāng)前移動距離
Offset transSize = Offset(0.0, 0.0);
/// 初始化動畫
_initAnim() {
_animationController = AnimationController(duration: Duration(seconds: 3), vsync: this);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
_animation.addListener(() {
// t 動態(tài)變化的值
var t = _animation.value;
if (mounted) {
setState(() {
_matrix4 = Matrix4.identity();
// 根據(jù)二階貝塞爾曲線計(jì)算移動軌跡點(diǎn)
double _left = pow(1 - t, 2) * p0.dx + 2 * t * (1 - t) * p1.dx + pow(t, 2) * p2.dx;
double _top = pow(1 - t, 2) * p0.dy + 2 * t * (1 - t) * p1.dy + pow(t, 2) * p2.dy;
// 設(shè)置移動
if (bezierCenter == null) {
transSize = Offset(0.0, 0.0);
} else {
transSize = Offset(transSize.dx - (bezierCenter.dx - _left),
transSize.dy - (bezierCenter.dy - _top));
}
_matrix4..translate(transSize.dx, transSize.dy, 0.0);
bezierCenter = Offset(_left, _top);
// 設(shè)置縮小倍數(shù)
_matrix4..scale((1-t) < 0.4 ? 0.4 : (1-t));
// 設(shè)置旋轉(zhuǎn)角度
double rotate = pi/2*t;
_matrix4..rotateX(rotate > rotate*0.8 ? rotate*0.8 : rotate);
_matrix4..rotateY(rotate > rotate*0.8 ? -rotate*0.8 : -rotate);
_matrix4..rotateZ(rotate > rotate*0.8 ? -rotate*0.8 : -rotate);
});
}
});
}
復(fù)制代碼