昨天有同学在QQ群里问一个动画效果怎么实现
想了一下,步骤如下:
- 画一条曲线
- 取到曲线上的点
- 有所有点信息了,想怎么画动画都可以
其实难点就是取到曲线上的点信息。还好Flutter的Path提供了一个computeMetrics的函数,通过这个函数我们可以拿到PathMetric,再通过PathMetric的getTangentForOffset函数拿到相关点的信息
核心的paint函数如下:
void paint(Canvas canvas, Size size) {
if (_path == null) {
return;
}
PathMetrics pms = _path.computeMetrics();
PathMetric pm = pms.elementAt(0);
double len = pm.length;
double linelen = len / 20;
double tmpStart = _fraction;
double end = (_fraction + linelen) > len ? len : (_fraction + linelen);
if (end >= len) {
tmpStart = len - linelen;
}
for (; tmpStart < end; tmpStart++) {
Tangent t = pm.getTangentForOffset(tmpStart);
_points.add(t.position);
}
canvas.drawPath(_path, _paint);
canvas.drawPoints(PointMode.points, _points, _paint2);
}
剩下的就是普通的Flutter动画控制了
上一个完成的效果gif
没有其他说的了,全部代码如下:
import 'dart:ui';
import 'package:flutter/material.dart';
class PathAnimation extends StatefulWidget {
@override
_PathAnimationState createState() => _PathAnimationState();
}
class _PathAnimationState extends State<PathAnimation>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
double _fraction = 0.0;
int _seconds = 6;
Path _path;
GlobalKey _canvasKey = GlobalKey();
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: _seconds));
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Path Animation'),
),
body: Container(
color: Colors.grey,
child: Column(
children: <Widget>[
CustomPaint(
painter: PathPainter(_path, _fraction),
child: Container(
key: _canvasKey,
height: 500.0,
),
),
RaisedButton(
onPressed: _startAnimation,
child: Text('Go'),
)
],
),
),
);
}
Path _getPath(Size size) {
Path path = Path();
path.moveTo(0, size.height / 2);
// path.quadraticBezierTo(
// size.width / 2, size.height, size.width, size.height / 2);
path.cubicTo(size.width / 4, 3 * size.height / 4, 3 * size.width / 4, size.height / 4, size.width, size.height);
return path;
}
_startAnimation() {
RenderBox renderBox = _canvasKey.currentContext.findRenderObject();
Size s = renderBox.size;
_path = _getPath(s);
PathMetrics pms = _path.computeMetrics();
PathMetric pm = pms.elementAt(0);
double len = pm.length;
_animation = Tween(begin: 0.0, end: len).animate(_controller)
..addListener(() {
setState(() {
_fraction = _animation.value;
});
})
..addStatusListener((status){
if (status == AnimationStatus.completed) {
_controller.stop();
}
});
_controller.forward();
}
}
class PathPainter extends CustomPainter {
double _fraction;
Path _path;
List<Offset> _points = List<Offset>();
PathPainter(this._path, this._fraction);
Paint _paint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
Paint _paint2 = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
@override
void paint(Canvas canvas, Size size) {
if (_path == null) {
return;
}
PathMetrics pms = _path.computeMetrics();
PathMetric pm = pms.elementAt(0);
double len = pm.length;
double linelen = len / 20;
double tmpStart = _fraction;
double end = (_fraction + linelen) > len ? len : (_fraction + linelen);
if (end >= len) {
tmpStart = len - linelen;
}
for (; tmpStart < end; tmpStart++) {
Tangent t = pm.getTangentForOffset(tmpStart);
_points.add(t.position);
}
canvas.drawPath(_path, _paint);
canvas.drawPoints(PointMode.points, _points, _paint2);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}