如何在我的小部件周围创建动画(圆角)矩形边框?
How can I create an animated (rounded) rectangular border around my widget?
如何使小部件的边框从 0% 变为 100%(类似于可以在 Adobe AfterEffects 中创建的 Trim 路径效果)?我想将其应用于具有矩形或圆角矩形形状的小部件。
下面是我试图实现的效果示例:
大图:
让我们将小部件包装在 CustomPaint
中。由于 CustomPaint
采用其子项的大小,我们不必担心在正确的位置绘制。
我们可以进一步以通用路径动画的精彩 作为起点并调整代码,以便我们的 AnimatedBorderPainter
可以绘制矩形、圆角矩形和圆形的路径。
最后,我们创建一个 AnimationController
并定义持续时间、曲线和我们需要的所有其他属性。
详情:
在 AnimatedBorderPainter
的 paint
方法中,我们首先在动画开始时创建 _originalPath
(即完整路径),然后(重新)绘制 currentPath
基于动画的进度。 _createAnimatedPath
方法取自上面提到的答案,其中有更详细的描述。
late Path _originalPath;
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
final animationPercent = _animation.value;
// Construct original path once when animation starts
if (animationPercent == 0.0) {
_originalPath = _createOriginalPath(size);
_paint = Paint()
..strokeWidth = _strokeWidth
..style = PaintingStyle.stroke
..color = _strokeColor;
}
final currentPath = _createAnimatedPath(
_originalPath,
animationPercent,
);
canvas.drawPath(currentPath, _paint);
}
让我们专注于为形状创建原始路径。我们可以使用 addRect
(矩形)、addRRect
(圆角矩形)和 addOval
(圆形)来创建相应的形状:
Path _createOriginalPath(Size size) {
switch (_pathType) {
case PathType.rect:
return _createOriginalPathRect(size);
case PathType.rRect:
return _createOriginalPathRRect(size);
case PathType.circle:
return _createOriginalPathCircle(size);
}
}
Path _createOriginalPathRect(Size size) {
Path originalPath = Path()
..addRect(
Rect.fromLTWH(0, 0, size.width, size.height),
)
..lineTo(0, -(_strokeWidth / 2));
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(
originalPath, PathType.rect, size);
}
return originalPath;
}
Path _createOriginalPathRRect(Size size) {
Path originalPath = Path()
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
_radius,
),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.rRect);
}
return originalPath;
}
Path _createOriginalPathCircle(Size size) {
Path originalPath = Path()
..addOval(
Rect.fromLTWH(0, 0, size.width, size.height),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.circle);
}
return originalPath;
}
由于我们还想定义我们的路径动画从哪里开始(使用startingPercentage
参数),我们必须根据输入剪切并重新加入我们最初构建的路径:
Path _createPathForStartingPercentage(Path originalPath, PathType pathType,
[Size? size]) {
// Assumes that original path consists of one subpath only
final pathMetrics = originalPath.computeMetrics().first;
final pathCutoffPoint = (_startingPercentage / 100) * pathMetrics.length;
final firstSubPath = pathMetrics.extractPath(0, pathCutoffPoint);
final secondSubPath =
pathMetrics.extractPath(pathCutoffPoint, pathMetrics.length);
if (pathType == PathType.rect) {
Path path = Path()
..addPath(secondSubPath, Offset.zero)
..lineTo(0, -(_strokeWidth / 2))
..addPath(firstSubPath, Offset.zero);
switch (_startingPercentage) {
case 25:
path.lineTo(size!.width + _strokeWidth / 2, 0);
break;
case 50:
path.lineTo(size!.width - _strokeWidth / 2, size.height);
break;
case 75:
path.lineTo(0, size!.height + _strokeWidth / 2);
break;
default:
}
return path;
}
return Path()
..addPath(secondSubPath, Offset.zero)
..addPath(firstSubPath, Offset.zero);
}
虽然还有更多内容(请参阅下面的完整代码),但我们基本上可以按如下方式使用我们的 AnimatedBorderPainter
,定义 startingPercentage
、animationDirection
和 radius
(后者仅与圆角矩形相关):
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller1,
strokeColor: Colors.black,
pathType: PathType.rRect,
animationDirection: AnimationDirection.clockwise,
startingPercentage: 40,
radius: const Radius.circular(12),
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation1,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
包含示例动画的完整代码,您可以在 DartPad 中 运行:
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Border Animation',
home: Scaffold(body: ExampleAnimatedBorderPainter()));
}
}
// Example code including two animations
class ExampleAnimatedBorderPainter extends StatefulWidget {
@override
State<ExampleAnimatedBorderPainter> createState() =>
_ExampleAnimatedBorderPainterState();
}
class _ExampleAnimatedBorderPainterState
extends State<ExampleAnimatedBorderPainter> with TickerProviderStateMixin {
late AnimationController _controller1;
late AnimationController _controller2;
@override
void initState() {
super.initState();
_controller1 = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 2000,
),
);
_controller2 = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 1500,
),
);
}
@override
void dispose() {
_controller1.dispose();
_controller2.dispose();
super.dispose();
}
void _startAnimation1() {
_controller1.reset();
_controller1.animateTo(1.0, curve: Curves.easeInOut);
}
void _startAnimation2() {
_controller2.reset();
_controller2.animateTo(1.0, curve: Curves.easeInOut);
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller1,
strokeColor: Colors.black,
pathType: PathType.rRect,
animationDirection: AnimationDirection.clockwise,
startingPercentage: 40,
radius: const Radius.circular(12),
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation1,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(
height: 20,
),
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller2,
strokeColor: Colors.deepOrange,
pathType: PathType.rRect,
animationDirection: AnimationDirection.counterclockwise,
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation2,
),
),
],
),
);
}
}
class AnimatedBorderPainter extends CustomPainter {
final Animation<double> _animation;
final PathType _pathType;
final double _strokeWidth;
final Color _strokeColor;
final Radius _radius;
final int _startingPercentage;
final AnimationDirection _animationDirection;
AnimatedBorderPainter({
required animation,
PathType pathType = PathType.rect,
double strokeWidth = 2.0,
Color strokeColor = Colors.blueGrey,
Radius radius = const Radius.circular(4.0),
int startingPercentage = 0,
AnimationDirection animationDirection = AnimationDirection.clockwise,
}) : assert(strokeWidth > 0, 'strokeWidth must be greater than 0.'),
assert(startingPercentage >= 0 && startingPercentage <= 100,
'startingPercentage must lie between 0 and 100.'),
_animation = animation,
_pathType = pathType,
_strokeWidth = strokeWidth,
_strokeColor = strokeColor,
_radius = radius,
_startingPercentage = startingPercentage,
_animationDirection = animationDirection,
super(repaint: animation);
late Path _originalPath;
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
final animationPercent = _animation.value;
// Construct original path once when animation starts
if (animationPercent == 0.0) {
_originalPath = _createOriginalPath(size);
_paint = Paint()
..strokeWidth = _strokeWidth
..style = PaintingStyle.stroke
..color = _strokeColor;
}
final currentPath = _createAnimatedPath(
_originalPath,
animationPercent,
);
canvas.drawPath(currentPath, _paint);
}
@override
bool shouldRepaint(AnimatedBorderPainter oldDelegate) => true;
Path _createOriginalPath(Size size) {
switch (_pathType) {
case PathType.rect:
return _createOriginalPathRect(size);
case PathType.rRect:
return _createOriginalPathRRect(size);
case PathType.circle:
return _createOriginalPathCircle(size);
}
}
Path _createOriginalPathRect(Size size) {
Path originalPath = Path()
..addRect(
Rect.fromLTWH(0, 0, size.width, size.height),
)
..lineTo(0, -(_strokeWidth / 2));
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(
originalPath, PathType.rect, size);
}
return originalPath;
}
Path _createOriginalPathRRect(Size size) {
Path originalPath = Path()
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
_radius,
),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.rRect);
}
return originalPath;
}
Path _createOriginalPathCircle(Size size) {
Path originalPath = Path()
..addOval(
Rect.fromLTWH(0, 0, size.width, size.height),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.circle);
}
return originalPath;
}
Path _createPathForStartingPercentage(Path originalPath, PathType pathType,
[Size? size]) {
// Assumes that original path consists of one subpath only
final pathMetrics = originalPath.computeMetrics().first;
final pathCutoffPoint = (_startingPercentage / 100) * pathMetrics.length;
final firstSubPath = pathMetrics.extractPath(0, pathCutoffPoint);
final secondSubPath =
pathMetrics.extractPath(pathCutoffPoint, pathMetrics.length);
if (pathType == PathType.rect) {
Path path = Path()
..addPath(secondSubPath, Offset.zero)
..lineTo(0, -(_strokeWidth / 2))
..addPath(firstSubPath, Offset.zero);
switch (_startingPercentage) {
case 25:
path.lineTo(size!.width + _strokeWidth / 2, 0);
break;
case 50:
path.lineTo(size!.width - _strokeWidth / 2, size.height);
break;
case 75:
path.lineTo(0, size!.height + _strokeWidth / 2);
break;
default:
}
return path;
}
return Path()
..addPath(secondSubPath, Offset.zero)
..addPath(firstSubPath, Offset.zero);
}
Path _createAnimatedPath(
Path originalPath,
double animationPercent,
) {
// ComputeMetrics can only be iterated once!
final totalLength = originalPath
.computeMetrics()
.fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
final currentLength = totalLength * animationPercent;
return _extractPathUntilLength(originalPath, currentLength);
}
Path _extractPathUntilLength(
Path originalPath,
double length,
) {
var currentLength = 0.0;
final path = Path();
var metricsIterator = _animationDirection == AnimationDirection.clockwise
? originalPath.computeMetrics().iterator
: originalPath.computeMetrics().toList().reversed.iterator;
while (metricsIterator.moveNext()) {
var metric = metricsIterator.current;
var nextLength = currentLength + metric.length;
final isLastSegment = nextLength > length;
if (isLastSegment) {
final remainingLength = length - currentLength;
final pathSegment = _animationDirection == AnimationDirection.clockwise
? metric.extractPath(0.0, remainingLength)
: metric.extractPath(
metric.length - remainingLength, metric.length);
path.addPath(pathSegment, Offset.zero);
break;
} else {
// There might be a more efficient way of extracting an entire path
final pathSegment = metric.extractPath(0.0, metric.length);
path.addPath(pathSegment, Offset.zero);
}
currentLength = nextLength;
}
return path;
}
}
enum PathType {
rect,
rRect,
circle,
}
enum AnimationDirection {
clockwise,
counterclockwise,
}
如何使小部件的边框从 0% 变为 100%(类似于可以在 Adobe AfterEffects 中创建的 Trim 路径效果)?我想将其应用于具有矩形或圆角矩形形状的小部件。
下面是我试图实现的效果示例:
大图:
让我们将小部件包装在 CustomPaint
中。由于 CustomPaint
采用其子项的大小,我们不必担心在正确的位置绘制。
我们可以进一步以通用路径动画的精彩 AnimatedBorderPainter
可以绘制矩形、圆角矩形和圆形的路径。
最后,我们创建一个 AnimationController
并定义持续时间、曲线和我们需要的所有其他属性。
详情:
在 AnimatedBorderPainter
的 paint
方法中,我们首先在动画开始时创建 _originalPath
(即完整路径),然后(重新)绘制 currentPath
基于动画的进度。 _createAnimatedPath
方法取自上面提到的答案,其中有更详细的描述。
late Path _originalPath;
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
final animationPercent = _animation.value;
// Construct original path once when animation starts
if (animationPercent == 0.0) {
_originalPath = _createOriginalPath(size);
_paint = Paint()
..strokeWidth = _strokeWidth
..style = PaintingStyle.stroke
..color = _strokeColor;
}
final currentPath = _createAnimatedPath(
_originalPath,
animationPercent,
);
canvas.drawPath(currentPath, _paint);
}
让我们专注于为形状创建原始路径。我们可以使用 addRect
(矩形)、addRRect
(圆角矩形)和 addOval
(圆形)来创建相应的形状:
Path _createOriginalPath(Size size) {
switch (_pathType) {
case PathType.rect:
return _createOriginalPathRect(size);
case PathType.rRect:
return _createOriginalPathRRect(size);
case PathType.circle:
return _createOriginalPathCircle(size);
}
}
Path _createOriginalPathRect(Size size) {
Path originalPath = Path()
..addRect(
Rect.fromLTWH(0, 0, size.width, size.height),
)
..lineTo(0, -(_strokeWidth / 2));
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(
originalPath, PathType.rect, size);
}
return originalPath;
}
Path _createOriginalPathRRect(Size size) {
Path originalPath = Path()
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
_radius,
),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.rRect);
}
return originalPath;
}
Path _createOriginalPathCircle(Size size) {
Path originalPath = Path()
..addOval(
Rect.fromLTWH(0, 0, size.width, size.height),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.circle);
}
return originalPath;
}
由于我们还想定义我们的路径动画从哪里开始(使用startingPercentage
参数),我们必须根据输入剪切并重新加入我们最初构建的路径:
Path _createPathForStartingPercentage(Path originalPath, PathType pathType,
[Size? size]) {
// Assumes that original path consists of one subpath only
final pathMetrics = originalPath.computeMetrics().first;
final pathCutoffPoint = (_startingPercentage / 100) * pathMetrics.length;
final firstSubPath = pathMetrics.extractPath(0, pathCutoffPoint);
final secondSubPath =
pathMetrics.extractPath(pathCutoffPoint, pathMetrics.length);
if (pathType == PathType.rect) {
Path path = Path()
..addPath(secondSubPath, Offset.zero)
..lineTo(0, -(_strokeWidth / 2))
..addPath(firstSubPath, Offset.zero);
switch (_startingPercentage) {
case 25:
path.lineTo(size!.width + _strokeWidth / 2, 0);
break;
case 50:
path.lineTo(size!.width - _strokeWidth / 2, size.height);
break;
case 75:
path.lineTo(0, size!.height + _strokeWidth / 2);
break;
default:
}
return path;
}
return Path()
..addPath(secondSubPath, Offset.zero)
..addPath(firstSubPath, Offset.zero);
}
虽然还有更多内容(请参阅下面的完整代码),但我们基本上可以按如下方式使用我们的 AnimatedBorderPainter
,定义 startingPercentage
、animationDirection
和 radius
(后者仅与圆角矩形相关):
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller1,
strokeColor: Colors.black,
pathType: PathType.rRect,
animationDirection: AnimationDirection.clockwise,
startingPercentage: 40,
radius: const Radius.circular(12),
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation1,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
包含示例动画的完整代码,您可以在 DartPad 中 运行:
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Border Animation',
home: Scaffold(body: ExampleAnimatedBorderPainter()));
}
}
// Example code including two animations
class ExampleAnimatedBorderPainter extends StatefulWidget {
@override
State<ExampleAnimatedBorderPainter> createState() =>
_ExampleAnimatedBorderPainterState();
}
class _ExampleAnimatedBorderPainterState
extends State<ExampleAnimatedBorderPainter> with TickerProviderStateMixin {
late AnimationController _controller1;
late AnimationController _controller2;
@override
void initState() {
super.initState();
_controller1 = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 2000,
),
);
_controller2 = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 1500,
),
);
}
@override
void dispose() {
_controller1.dispose();
_controller2.dispose();
super.dispose();
}
void _startAnimation1() {
_controller1.reset();
_controller1.animateTo(1.0, curve: Curves.easeInOut);
}
void _startAnimation2() {
_controller2.reset();
_controller2.animateTo(1.0, curve: Curves.easeInOut);
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller1,
strokeColor: Colors.black,
pathType: PathType.rRect,
animationDirection: AnimationDirection.clockwise,
startingPercentage: 40,
radius: const Radius.circular(12),
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation1,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(
height: 20,
),
CustomPaint(
foregroundPainter: AnimatedBorderPainter(
animation: _controller2,
strokeColor: Colors.deepOrange,
pathType: PathType.rRect,
animationDirection: AnimationDirection.counterclockwise,
),
child: ElevatedButton(
child: const Text('Click me also!'),
onPressed: _startAnimation2,
),
),
],
),
);
}
}
class AnimatedBorderPainter extends CustomPainter {
final Animation<double> _animation;
final PathType _pathType;
final double _strokeWidth;
final Color _strokeColor;
final Radius _radius;
final int _startingPercentage;
final AnimationDirection _animationDirection;
AnimatedBorderPainter({
required animation,
PathType pathType = PathType.rect,
double strokeWidth = 2.0,
Color strokeColor = Colors.blueGrey,
Radius radius = const Radius.circular(4.0),
int startingPercentage = 0,
AnimationDirection animationDirection = AnimationDirection.clockwise,
}) : assert(strokeWidth > 0, 'strokeWidth must be greater than 0.'),
assert(startingPercentage >= 0 && startingPercentage <= 100,
'startingPercentage must lie between 0 and 100.'),
_animation = animation,
_pathType = pathType,
_strokeWidth = strokeWidth,
_strokeColor = strokeColor,
_radius = radius,
_startingPercentage = startingPercentage,
_animationDirection = animationDirection,
super(repaint: animation);
late Path _originalPath;
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
final animationPercent = _animation.value;
// Construct original path once when animation starts
if (animationPercent == 0.0) {
_originalPath = _createOriginalPath(size);
_paint = Paint()
..strokeWidth = _strokeWidth
..style = PaintingStyle.stroke
..color = _strokeColor;
}
final currentPath = _createAnimatedPath(
_originalPath,
animationPercent,
);
canvas.drawPath(currentPath, _paint);
}
@override
bool shouldRepaint(AnimatedBorderPainter oldDelegate) => true;
Path _createOriginalPath(Size size) {
switch (_pathType) {
case PathType.rect:
return _createOriginalPathRect(size);
case PathType.rRect:
return _createOriginalPathRRect(size);
case PathType.circle:
return _createOriginalPathCircle(size);
}
}
Path _createOriginalPathRect(Size size) {
Path originalPath = Path()
..addRect(
Rect.fromLTWH(0, 0, size.width, size.height),
)
..lineTo(0, -(_strokeWidth / 2));
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(
originalPath, PathType.rect, size);
}
return originalPath;
}
Path _createOriginalPathRRect(Size size) {
Path originalPath = Path()
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
_radius,
),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.rRect);
}
return originalPath;
}
Path _createOriginalPathCircle(Size size) {
Path originalPath = Path()
..addOval(
Rect.fromLTWH(0, 0, size.width, size.height),
);
if (_startingPercentage > 0 && _startingPercentage < 100) {
return _createPathForStartingPercentage(originalPath, PathType.circle);
}
return originalPath;
}
Path _createPathForStartingPercentage(Path originalPath, PathType pathType,
[Size? size]) {
// Assumes that original path consists of one subpath only
final pathMetrics = originalPath.computeMetrics().first;
final pathCutoffPoint = (_startingPercentage / 100) * pathMetrics.length;
final firstSubPath = pathMetrics.extractPath(0, pathCutoffPoint);
final secondSubPath =
pathMetrics.extractPath(pathCutoffPoint, pathMetrics.length);
if (pathType == PathType.rect) {
Path path = Path()
..addPath(secondSubPath, Offset.zero)
..lineTo(0, -(_strokeWidth / 2))
..addPath(firstSubPath, Offset.zero);
switch (_startingPercentage) {
case 25:
path.lineTo(size!.width + _strokeWidth / 2, 0);
break;
case 50:
path.lineTo(size!.width - _strokeWidth / 2, size.height);
break;
case 75:
path.lineTo(0, size!.height + _strokeWidth / 2);
break;
default:
}
return path;
}
return Path()
..addPath(secondSubPath, Offset.zero)
..addPath(firstSubPath, Offset.zero);
}
Path _createAnimatedPath(
Path originalPath,
double animationPercent,
) {
// ComputeMetrics can only be iterated once!
final totalLength = originalPath
.computeMetrics()
.fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
final currentLength = totalLength * animationPercent;
return _extractPathUntilLength(originalPath, currentLength);
}
Path _extractPathUntilLength(
Path originalPath,
double length,
) {
var currentLength = 0.0;
final path = Path();
var metricsIterator = _animationDirection == AnimationDirection.clockwise
? originalPath.computeMetrics().iterator
: originalPath.computeMetrics().toList().reversed.iterator;
while (metricsIterator.moveNext()) {
var metric = metricsIterator.current;
var nextLength = currentLength + metric.length;
final isLastSegment = nextLength > length;
if (isLastSegment) {
final remainingLength = length - currentLength;
final pathSegment = _animationDirection == AnimationDirection.clockwise
? metric.extractPath(0.0, remainingLength)
: metric.extractPath(
metric.length - remainingLength, metric.length);
path.addPath(pathSegment, Offset.zero);
break;
} else {
// There might be a more efficient way of extracting an entire path
final pathSegment = metric.extractPath(0.0, metric.length);
path.addPath(pathSegment, Offset.zero);
}
currentLength = nextLength;
}
return path;
}
}
enum PathType {
rect,
rRect,
circle,
}
enum AnimationDirection {
clockwise,
counterclockwise,
}