• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math';
6
7import 'package:flutter/widgets.dart';
8import 'package:flutter/animation.dart';
9import 'package:flutter/material.dart';
10
11// Based on https://github.com/eseidelGoogle/bezier_perf/blob/master/lib/main.dart
12class CubicBezierPage extends StatelessWidget {
13  @override
14  Widget build(BuildContext context) {
15    return Center(
16      child: Column(
17        mainAxisAlignment: MainAxisAlignment.center,
18        children: const <Widget>[
19          Bezier(Colors.amber, 1.0),
20        ],
21      ),
22    );
23  }
24}
25
26class Bezier extends StatelessWidget {
27  const Bezier(this.color, this.scale, {this.blur = 0.0, this.delay = 0.0});
28
29  final Color color;
30  final double scale;
31  final double blur;
32  final double delay;
33
34  List<PathDetail> _getLogoPath() {
35    final List<PathDetail> paths = <PathDetail>[];
36
37    final Path path = Path();
38    path.moveTo(100.0, 97.0);
39    path.cubicTo(100.0, 97.0, 142.0, 59.0, 169.91, 41.22);
40    path.cubicTo(197.82, 23.44, 249.24, 5.52, 204.67, 85.84);
41
42    paths.add(PathDetail(path));
43
44    // Path 2
45    final Path bezier2Path = Path();
46    bezier2Path.moveTo(0.0, 70.55);
47    bezier2Path.cubicTo(0.0, 70.55, 42.0, 31.55, 69.91, 14.77);
48    bezier2Path.cubicTo(97.82, -2.01, 149.24, -20.93, 104.37, 59.39);
49
50    paths.add(PathDetail(bezier2Path,
51        translate: <double>[29.45, 151.0], rotation: -1.5708));
52
53    // Path 3
54    final Path bezier3Path = Path();
55    bezier3Path.moveTo(0.0, 69.48);
56    bezier3Path.cubicTo(0.0, 69.48, 44.82, 27.92, 69.91, 13.7);
57    bezier3Path.cubicTo(95.0, -0.52, 149.24, -22.0, 104.37, 58.32);
58
59    paths.add(PathDetail(bezier3Path,
60        translate: <double>[53.0, 200.48], rotation: -3.14159));
61
62    // Path 4
63    final Path bezier4Path = Path();
64    bezier4Path.moveTo(0.0, 69.48);
65    bezier4Path.cubicTo(0.0, 69.48, 43.82, 27.92, 69.91, 13.7);
66    bezier4Path.cubicTo(96.0, -0.52, 149.24, -22.0, 104.37, 58.32);
67
68    paths.add(PathDetail(bezier4Path,
69        translate: <double>[122.48, 77.0], rotation: -4.71239));
70
71    return paths;
72  }
73
74  @override
75  Widget build(BuildContext context) {
76    return Stack(children: <Widget>[
77      CustomPaint(
78        foregroundPainter:
79        BezierPainter(Colors.grey, 0.0, _getLogoPath(), false),
80        size: const Size(100.0, 100.0),
81      ),
82      AnimatedBezier(color, scale, blur: blur, delay: delay),
83    ]);
84  }
85}
86
87class PathDetail {
88  PathDetail(this.path, {this.translate, this.rotation});
89
90  Path path;
91  List<double> translate = <double>[];
92  double rotation;
93}
94
95class AnimatedBezier extends StatefulWidget {
96  const AnimatedBezier(this.color, this.scale, {this.blur = 0.0, this.delay});
97
98  final Color color;
99  final double scale;
100  final double blur;
101  final double delay;
102
103  @override
104  State createState() => AnimatedBezierState();
105}
106
107class Point {
108  Point(this.x, this.y);
109
110  double x;
111  double y;
112}
113
114class AnimatedBezierState extends State<AnimatedBezier>
115    with SingleTickerProviderStateMixin {
116  double scale;
117  AnimationController controller;
118  CurvedAnimation curve;
119  bool isPlaying = false;
120  List<List<Point>> pointList = <List<Point>>[
121    <Point>[],
122    <Point>[],
123    <Point>[],
124    <Point>[],
125  ];
126  bool isReversed = false;
127
128  List<PathDetail> _playForward() {
129    final List<PathDetail> paths = <PathDetail>[];
130    final double t = curve.value;
131    final double b = controller.upperBound;
132    double pX;
133    double pY;
134
135    final Path path = Path();
136
137    if (t < b / 2) {
138      pX = _getCubicPoint(t * 2, 100.0, 100.0, 142.0, 169.91);
139      pY = _getCubicPoint(t * 2, 97.0, 97.0, 59.0, 41.22);
140      pointList[0].add(Point(pX, pY));
141    } else {
142      pX = _getCubicPoint(t * 2 - b, 169.91, 197.80, 249.24, 204.67);
143      pY = _getCubicPoint(t * 2 - b, 41.22, 23.44, 5.52, 85.84);
144      pointList[0].add(Point(pX, pY));
145    }
146
147    path.moveTo(100.0, 97.0);
148
149    for (Point p in pointList[0]) {
150      path.lineTo(p.x, p.y);
151    }
152
153    paths.add(PathDetail(path));
154
155    // Path 2
156    final Path bezier2Path = Path();
157
158    if (t <= b / 2) {
159      final double pX = _getCubicPoint(t * 2, 0.0, 0.0, 42.0, 69.91);
160      final double pY = _getCubicPoint(t * 2, 70.55, 70.55, 31.55, 14.77);
161      pointList[1].add(Point(pX, pY));
162    } else {
163      final double pX = _getCubicPoint(t * 2 - b, 69.91, 97.82, 149.24, 104.37);
164      final double pY = _getCubicPoint(t * 2 - b, 14.77, -2.01, -20.93, 59.39);
165      pointList[1].add(Point(pX, pY));
166    }
167
168    bezier2Path.moveTo(0.0, 70.55);
169
170    for (Point p in pointList[1]) {
171      bezier2Path.lineTo(p.x, p.y);
172    }
173
174    paths.add(PathDetail(bezier2Path,
175        translate: <double>[29.45, 151.0], rotation: -1.5708));
176
177    // Path 3
178    final Path bezier3Path = Path();
179    if (t <= b / 2) {
180      pX = _getCubicPoint(t * 2, 0.0, 0.0, 44.82, 69.91);
181      pY = _getCubicPoint(t * 2, 69.48, 69.48, 27.92, 13.7);
182      pointList[2].add(Point(pX, pY));
183    } else {
184      pX = _getCubicPoint(t * 2 - b, 69.91, 95.0, 149.24, 104.37);
185      pY = _getCubicPoint(t * 2 - b, 13.7, -0.52, -22.0, 58.32);
186      pointList[2].add(Point(pX, pY));
187    }
188
189    bezier3Path.moveTo(0.0, 69.48);
190
191    for (Point p in pointList[2]) {
192      bezier3Path.lineTo(p.x, p.y);
193    }
194
195    paths.add(PathDetail(bezier3Path,
196        translate: <double>[53.0, 200.48], rotation: -3.14159));
197
198    // Path 4
199    final Path bezier4Path = Path();
200
201    if (t < b / 2) {
202      final double pX = _getCubicPoint(t * 2, 0.0, 0.0, 43.82, 69.91);
203      final double pY = _getCubicPoint(t * 2, 69.48, 69.48, 27.92, 13.7);
204      pointList[3].add(Point(pX, pY));
205    } else {
206      final double pX = _getCubicPoint(t * 2 - b, 69.91, 96.0, 149.24, 104.37);
207      final double pY = _getCubicPoint(t * 2 - b, 13.7, -0.52, -22.0, 58.32);
208      pointList[3].add(Point(pX, pY));
209    }
210
211    bezier4Path.moveTo(0.0, 69.48);
212
213    for (Point p in pointList[3]) {
214      bezier4Path.lineTo(p.x, p.y);
215    }
216
217    paths.add(PathDetail(bezier4Path,
218        translate: <double>[122.48, 77.0], rotation: -4.71239));
219
220    return paths;
221  }
222
223  List<PathDetail> _playReversed() {
224    for (List<Point> list in pointList) {
225      if (list.isNotEmpty) {
226        list.removeLast();
227      }
228    }
229
230    final List<Point> points = pointList[0];
231    final Path path = Path();
232
233    path.moveTo(100.0, 97.0);
234
235    for (Point point in points) {
236      path.lineTo(point.x, point.y);
237    }
238
239    final Path bezier2Path = Path();
240
241    bezier2Path.moveTo(0.0, 70.55);
242
243    for (Point p in pointList[1]) {
244      bezier2Path.lineTo(p.x, p.y);
245    }
246
247    final Path bezier3Path = Path();
248    bezier3Path.moveTo(0.0, 69.48);
249
250    for (Point p in pointList[2]) {
251      bezier3Path.lineTo(p.x, p.y);
252    }
253
254    final Path bezier4Path = Path();
255
256    bezier4Path.moveTo(0.0, 69.48);
257
258    for (Point p in pointList[3]) {
259      bezier4Path.lineTo(p.x, p.y);
260    }
261
262    return <PathDetail>[
263      PathDetail(path),
264      PathDetail(bezier2Path, translate: <double>[29.45, 151.0], rotation: -1.5708),
265      PathDetail(bezier3Path,
266          translate: <double>[53.0, 200.48], rotation: -3.14159),
267      PathDetail(bezier4Path, translate: <double>[122.48, 77.0], rotation: -4.71239),
268    ];
269  }
270
271  List<PathDetail> _getLogoPath() {
272    if (!isReversed) {
273      return _playForward();
274    }
275
276    return _playReversed();
277  }
278
279  //From http://wiki.roblox.com/index.php?title=File:Beziereq4.png
280  double _getCubicPoint(double t, double p0, double p1, double p2, double p3) {
281    return pow(1 - t, 3) * p0 +
282        3 * pow(1 - t, 2) * t * p1 +
283        3 * (1 - t) * pow(t, 2) * p2 +
284        pow(t, 3) * p3;
285  }
286
287  void playAnimation() {
288    isPlaying = true;
289    isReversed = false;
290    for (List<Point> list in pointList) {
291      list.clear();
292    }
293    controller.reset();
294    controller.forward();
295  }
296
297  void stopAnimation() {
298    isPlaying = false;
299    controller.stop();
300    for (List<Point> list in pointList) {
301      list.clear();
302    }
303  }
304
305  void reverseAnimation() {
306    isReversed = true;
307    controller.reverse();
308  }
309
310  @override
311  void initState() {
312    super.initState();
313    controller = AnimationController(
314        vsync: this, duration: const Duration(milliseconds: 1000));
315    curve = CurvedAnimation(parent: controller, curve: Curves.linear)
316      ..addListener(() {
317        setState(() {});
318      })
319      ..addStatusListener((AnimationStatus state) {
320        if (state == AnimationStatus.completed) {
321          reverseAnimation();
322        } else if (state == AnimationStatus.dismissed) {
323          playAnimation();
324        }
325      });
326
327    playAnimation();
328  }
329
330  @override
331  void dispose() {
332    controller.dispose();
333    super.dispose();
334  }
335
336  @override
337  Widget build(BuildContext context) {
338    return CustomPaint(
339        foregroundPainter: BezierPainter(widget.color,
340            curve.value * widget.blur, _getLogoPath(), isPlaying),
341        size: const Size(100.0, 100.0));
342  }
343}
344
345class BezierPainter extends CustomPainter {
346  BezierPainter(this.color, this.blur, this.path, this.isPlaying);
347
348  Color color;
349  final double blur;
350  List<PathDetail> path;
351  bool isPlaying;
352
353  @override
354  void paint(Canvas canvas, Size size) {
355    final Paint paint = Paint();
356    paint.strokeWidth = 18.0;
357    paint.style = PaintingStyle.stroke;
358    paint.strokeCap = StrokeCap.round;
359    paint.color = color;
360    canvas.scale(0.5, 0.5);
361
362    for (int i = 0; i < path.length; i++) {
363      if (path[i].translate != null) {
364        canvas.translate(path[i].translate[0], path[i].translate[1]);
365      }
366
367      if (path[i].rotation != null) {
368        canvas.rotate(path[i].rotation);
369      }
370
371      if (blur > 0) {
372        final MaskFilter blur = MaskFilter.blur(BlurStyle.normal, this.blur);
373        paint.maskFilter = blur;
374        canvas.drawPath(path[i].path, paint);
375      }
376
377      paint.maskFilter = null;
378      canvas.drawPath(path[i].path, paint);
379    }
380  }
381
382  @override
383  bool shouldRepaint(BezierPainter oldDelegate) => true;
384
385  @override
386  bool shouldRebuildSemantics(BezierPainter oldDelegate) => false;
387}
388