← Назад к вопросам

Реализовать кастомный Progress Indicator

2.2 Middle🔥 91 комментариев
#Flutter виджеты#Анимации

Условие

Создайте кастомный виджет индикатора прогресса с использованием CustomPainter.

Требования

  1. Круговой индикатор прогресса
  2. Настраиваемый цвет, толщина линии, размер
  3. Отображение процента в центре
  4. Анимированное изменение прогресса
  5. Возможность использовать как determinate и indeterminate

Интерфейс

CustomProgressIndicator({
  required double progress, // 0.0 - 1.0
  Color color = Colors.blue,
  Color backgroundColor = Colors.grey,
  double strokeWidth = 4.0,
  bool showPercentage = true,
})

Дополнительные баллы

  • Градиентный цвет прогресса
  • Разные стили конца линии (round, square)
  • Пульсирующий эффект при завершении

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Кастомный Progress Indicator

Создаём круговой индикатор прогресса с поддержкой градиентов и анимаций.

CustomPainter для рисования

Используем Canvas для отрисовки окружности и прогрессивной дуги:

class CircularProgressPainter extends CustomPainter {
  final double progress;
  final Color progressColor;
  final Color backgroundColor;
  final double strokeWidth;

  CircularProgressPainter({
    required this.progress,
    required this.progressColor,
    required this.backgroundColor,
    required this.strokeWidth,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    // Фоновая окружность
    canvas.drawCircle(
      center,
      radius,
      Paint()
        ..color = backgroundColor
        ..style = PaintingStyle.stroke
        ..strokeWidth = strokeWidth,
    );

    // Дуга прогресса
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -3.14159 / 2,
      2 * 3.14159 * progress,
      false,
      Paint()
        ..color = progressColor
        ..style = PaintingStyle.stroke
        ..strokeWidth = strokeWidth
        ..strokeCap = StrokeCap.round,
    );
  }

  @override
  bool shouldRepaint(CircularProgressPainter oldDelegate) {
    return oldDelegate.progress != progress;
  }
}

Основной виджет

class CustomProgressIndicator extends StatefulWidget {
  final double progress;
  final Color color;
  final Color backgroundColor;
  final double strokeWidth;
  final bool showPercentage;
  final double size;

  const CustomProgressIndicator({
    required this.progress,
    this.color = Colors.blue,
    this.backgroundColor = Colors.grey,
    this.strokeWidth = 4.0,
    this.showPercentage = true,
    this.size = 200,
  });

  @override
  _CustomProgressIndicatorState createState() =>
      _CustomProgressIndicatorState();
}

class _CustomProgressIndicatorState extends State<CustomProgressIndicator>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 800),
      vsync: this,
    );
    _setupAnimation();
    _controller.forward();
  }

  void _setupAnimation() {
    _animation = Tween<double>(begin: 0, end: widget.progress).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  void didUpdateWidget(CustomProgressIndicator oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.progress != widget.progress) {
      _controller.reset();
      _setupAnimation();
      _controller.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.size,
      height: widget.size,
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Stack(
            alignment: Alignment.center,
            children: [
              CustomPaint(
                painter: CircularProgressPainter(
                  progress: _animation.value,
                  progressColor: widget.color,
                  backgroundColor: widget.backgroundColor,
                  strokeWidth: widget.strokeWidth,
                ),
              ),
              if (widget.showPercentage)
                Text(
                  '${(_animation.value * 100).toStringAsFixed(0)}%',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                    color: widget.color,
                  ),
                ),
            ],
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Примеры использования

class ProgressDemo extends StatefulWidget {
  @override
  _ProgressDemoState createState() => _ProgressDemoState();
}

class _ProgressDemoState extends State<ProgressDemo> {
  double _progress = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Progress Indicator')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CustomProgressIndicator(
              progress: _progress,
              color: Colors.blue,
              size: 200,
            ),
            SizedBox(height: 40),
            Slider(
              value: _progress,
              onChanged: (value) {
                setState(() => _progress = value);
              },
            ),
          ],
        ),
      ),
    );
  }
}

Этот индикатор можно легко расширить для градиентов и различных стилей конца линии.

Реализовать кастомный Progress Indicator | PrepBro