创建带有圆角矩形的进度指示器

问题描述 投票:0回答:2

我正在尝试在我的应用程序中创建一个圆角矩形进度指示器。我以前实现过一个圆形指示器,但不喜欢这个形状。我希望它看起来像这样(起点位于顶部):

enter image description here

但是我用

0
作为图层的
.strokeStart
属性得到了这个:

enter image description here

我当前的代码位置在

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
是“投票”按钮。

与创建圆形进度指示器不同,我无法在贝塞尔曲线路径的初始化中设置开始和结束角度。

如何使进度从第一张图片中所示的顶部中间开始?

编辑 - 添加了一张没有圆角半径的图片:

enter image description here

看来是圆角半径造成了问题。


如有疑问请追问!

swift formula cashapelayer
2个回答
2
投票

我找到了一个解决方案,使加载指示器适用于圆角:

enter image description here

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
让动画等待您将其删除。

我希望这个公式对未来的任何人都有帮助!

如果您需要帮助,可以随时给我留言,我很乐意提供帮助。 :)


0
投票

这是一个可定制的

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;
}
© www.soinside.com 2019 - 2024. All rights reserved.