我正在尝试在我的应用程序中创建一个圆角矩形进度指示器。我以前实现过一个圆形指示器,但不喜欢这个形状。我希望它看起来像这样(起点位于顶部):
但是我用
0
作为图层的 .strokeStart
属性得到了这个:
我当前的代码位置在
viewDidLoad()
:
let queueShapeLayer = CAShapeLayer()
let queuePath = UIBezierPath(roundedRect: addToQueue.frame, cornerRadius: addToQueue.layer.cornerRadius)
queueShapeLayer.path = queuePath.cgPath
queueShapeLayer.lineWidth = 5
queueShapeLayer.strokeColor = UIColor.white.cgColor
queueShapeLayer.fillColor = UIColor.clear.cgColor
queueShapeLayer.strokeStart = 0
queueShapeLayer.strokeEnd = 0.5
view.layer.addSublayer(queueShapeLayer)
addToQueue
是“投票”按钮。
与创建圆形进度指示器不同,我无法在贝塞尔曲线路径的初始化中设置开始和结束角度。
如何使进度从第一张图片中所示的顶部中间开始?
编辑 - 添加了一张没有圆角半径的图片:
看来是圆角半径造成了问题。
如有疑问请追问!
我找到了一个解决方案,使加载指示器适用于圆角:
let queueShapeLayer = CAShapeLayer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Queue timer
let radius = addToQueue.layer.cornerRadius
let diameter = radius * 2
let totalLength = (addToQueue.frame.width - diameter) * 2 + (CGFloat.pi * diameter)
let queuePath = UIBezierPath(roundedRect: addToQueue.frame, cornerRadius: radius)
queueShapeLayer.path = queuePath.cgPath
queueShapeLayer.lineWidth = 5
queueShapeLayer.strokeColor = UIColor.white.cgColor
queueShapeLayer.fillColor = UIColor.clear.cgColor
queueShapeLayer.strokeStart = 0.25 - CGFloat.pi * diameter / 3 / totalLength // Change the '0.25' to 0.5, 0.75 etc. wherever you want the bar to start
queueShapeLayer.strokeEnd = queueShapeLayer.strokeStart + 0.5 // Change this to the value you want it to go to (in this case 0.5 or 50% loaded)
view.layer.addSublayer(queueShapeLayer)
}
完成此操作后,我遇到了无法全程制作动画的问题。为了解决这个问题,我创建了第二个动画(将
strokeStart
设置为 0
),然后放置了 完成块,这样我就可以在正确的时间触发动画。
提示:
使用
animation.fillMode = CAMediaTimingFillMode.forwards
时添加 animation.isRemovedOnCompletion = false
和 CABasicAnimation
让动画等待您将其删除。
我希望这个公式对未来的任何人都有帮助!
如果您需要帮助,可以随时给我留言,我很乐意提供帮助。 :)
这是一个可定制的
RoundedRectangleLoadingIndicator
小部件的完整实现,它的动画线长度增加和减少,感觉很好:
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class RoundedRectangleLoadingIndicator extends StatefulWidget {
final double height;
final double width;
final double borderRadius;
final Color? color;
final Color? backgroundColor;
final double strokeWidth;
const RoundedRectangleLoadingIndicator({
super.key,
this.height = 50.0,
this.width = 200.0,
this.borderRadius = 36.0,
this.color,
this.backgroundColor,
this.strokeWidth = 4.0,
});
@override
_RoundedRectangleLoadingIndicatorState createState() =>
_RoundedRectangleLoadingIndicatorState();
}
class _RoundedRectangleLoadingIndicatorState
extends State<RoundedRectangleLoadingIndicator>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final themeData = Theme.of(context);
return Center(
child: SizedBox(
width: widget.width,
height: widget.height,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: RoundedRectangleLoadingPainter(
progress: _controller.value,
borderRadius: widget.borderRadius,
color: widget.color ?? themeData.primaryColor,
backgroundColor: widget.backgroundColor ??
themeData.scaffoldBackgroundColor,
strokeWidth: widget.strokeWidth,
height: widget.height,
width: widget.width),
);
},
),
),
);
}
}
class RoundedRectangleLoadingPainter extends CustomPainter {
final double progress;
final double borderRadius;
final Color color;
final Color backgroundColor;
final double strokeWidth;
final double height;
final double width;
RoundedRectangleLoadingPainter({
required this.progress,
required this.borderRadius,
required this.color,
required this.backgroundColor,
required this.strokeWidth,
required this.height,
required this.width,
});
@override
void paint(Canvas canvas, Size size) {
final Paint borderPaint = Paint()
..color = backgroundColor
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth;
final Paint loadingPaint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeWidth = strokeWidth;
final Rect rect = Rect.fromLTWH(
strokeWidth / 2,
strokeWidth / 2,
size.width - strokeWidth,
size.height - strokeWidth,
);
final RRect roundedRect = RRect.fromRectAndRadius(
rect,
Radius.circular(borderRadius),
);
canvas.drawRRect(roundedRect, borderPaint);
final Path borderPath = Path()..addRRect(roundedRect);
final PathMetric pathMetric = borderPath.computeMetrics().first;
final double pathLength = pathMetric.length;
final double minLength = max(30, min(height, width));
final double maxLength = max(height, width) * 0.7;
final double start = (progress * pathLength) % pathLength;
final double segmentLength = lerpDouble(
minLength,
maxLength,
(sin(progress * 2 * pi) + 1) / 2,
)!;
if (start + segmentLength <= pathLength) {
// If the segment doesn't exceed the path boundary, draw it directly
final Path extractPath =
pathMetric.extractPath(start, start + segmentLength);
canvas.drawPath(extractPath, loadingPaint);
} else {
// If the segment exceeds the path boundary, split it into two
final double firstSegmentEnd = pathLength;
final double secondSegmentStart = 0;
final double secondSegmentEnd = (start + segmentLength) % pathLength;
final Path firstSegment = pathMetric.extractPath(start, firstSegmentEnd);
final Path secondSegment =
pathMetric.extractPath(secondSegmentStart, secondSegmentEnd);
canvas.drawPath(firstSegment, loadingPaint);
canvas.drawPath(secondSegment, loadingPaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}