segmented_ring_painter

Pub Version Pub Points Live Demo Pub Likes CI License


✨ Features

  • πŸƒ Concentric Activity Rings (NestedActivityRings): Apple Fitness-style nested rings with authentic overlap drop-shadows on overachievement (>100% goal).
  • 🍰 Proportional & Segmented Rings (SegmentedRingChart): Dynamic multi-segment progress circles with precise angular gap spacing (perfect for Macro/Nutrition breakdowns, Budget allocation, or Multi-phase workflows).
  • 🎨 Rich Styling: Solid colors, sweep gradients, outer glow filters, and customizable end caps (RingCapStyle.round, butt, square).
  • πŸ‘† Built-in Touch Hit-Testing: Tap any individual segment or concentric ring to trigger tooltips, detail sheets, and selection states.
  • ⚑ Buttery Smooth: Hardware-accelerated CustomPainter with strict shouldRepaint dirty checks to ensure steady 60/120 FPS.
  • 🌐 All 6 Platforms Supported: 100% pure Dart & Flutter with zero native C/C++ dependencies. Works out-of-the-box on Android, iOS, Web, macOS, Windows, and Linux.

πŸ“¦ Installation

Add segmented_ring_painter to your pubspec.yaml:

dependencies:
  segmented_ring_painter: ^1.1.2

Or run:

flutter pub add segmented_ring_painter

πŸš€ Quick Start

1. Concentric Activity Rings (Apple Fitness Style)

import 'package:flutter/material.dart';
import 'package:segmented_ring_painter/segmented_ring_painter.dart';

Widget buildActivityRings() {
  return NestedActivityRings(
    width: 220,
    height: 220,
    rings: [
      NestedRing(
        value: 520,
        maxValue: 500, // 104% -> Overachieved!
        color: const Color(0xFFFA114F),
        label: 'Move',
      ),
      NestedRing(
        value: 38,
        maxValue: 30,
        color: const Color(0xFFAAF724),
        label: 'Exercise',
      ),
      NestedRing(
        value: 9,
        maxValue: 12,
        color: const Color(0xFF00F5D4),
        label: 'Stand',
      ),
    ],
    style: const NestedRingsStyle(
      strokeWidth: 20.0,
      ringSpacing: 4.0,
      enableOverachievementShadow: true,
    ),
    center: const Icon(Icons.local_fire_department, color: Color(0xFFFA114F), size: 32),
    onRingTap: (index, ring) {
      print('Tapped ${ring.label}: ${ring.value}/${ring.maxValue}');
    },
  );
}

2. Segmented Proportional Ring (Nutrition / Budget)

import 'package:flutter/material.dart';
import 'package:segmented_ring_painter/segmented_ring_painter.dart';

Widget buildMacroBreakdown() {
  return SegmentedRingChart(
    width: 220,
    height: 220,
    segments: [
      RingSegment(
        value: 180 * 4, // Carbs kcal
        color: const Color(0xFF38BDF8),
        label: 'Carbs',
        shadowColor: const Color(0xFF38BDF8),
        shadowBlur: 6.0,
      ),
      RingSegment(
        value: 140 * 4, // Protein kcal
        color: const Color(0xFF34D399),
        label: 'Protein',
        shadowColor: const Color(0xFF34D399),
        shadowBlur: 6.0,
      ),
      RingSegment(
        value: 65 * 9, // Fat kcal
        color: const Color(0xFFFBBF24),
        label: 'Fats',
        shadowColor: const Color(0xFFFBBF24),
        shadowBlur: 6.0,
      ),
    ],
    style: const SegmentedRingStyle(
      strokeWidth: 22.0,
      gapAngle: 5.0, // 5Β° angular gap between segments
      capStyle: RingCapStyle.round,
    ),
    center: const Text(
      '1,865\nkcal',
      textAlign: TextAlign.center,
      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
    ),
    onSegmentTap: (index, segment) {
      print('Selected segment: ${segment.label} (${segment.value} kcal)');
    },
  );
}

πŸ› οΈ API Reference

NestedActivityRings Properties

Property Type Default Description
rings List<NestedRing> Required Concentric ring configurations from outer to inner.
style NestedRingsStyle NestedRingsStyle() Stroke thickness, spacing, shadows, and cap styling.
center Widget? null Optional widget anchored at the center of the innermost ring.
animationDuration Duration Duration(milliseconds: 1000) Duration for entrance & value change animations.
animationCurve Curve Curves.easeOutCubic Interpolation curve for smooth physics.
onRingTap Function(int, NestedRing)? null Callback returning the tapped ring index and data.

SegmentedRingChart Properties

Property Type Default Description
segments List<RingSegment> Required The data segments to partition across the ring.
style SegmentedRingStyle SegmentedRingStyle() Stroke width, angular gap, background track, glow.
mode SegmentedRingMode SegmentedRingMode.proportional proportional (auto-sums to 100%) or absolute.
totalValue double? null Target total capacity for SegmentedRingMode.absolute.
center Widget? null Optional center widget.
onSegmentTap Function(int, RingSegment)? null Callback returning the tapped segment index and data.

πŸ’– Support the Project

If you find this project useful, consider supporting its active maintenance and future development:

Buy Me A Coffee GitHub Sponsors Patreon


πŸ“„ License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Maintained with ❀️ by Govind Tank.

Libraries

segmented_ring_painter
Production-ready segmented circular progress and concentric activity ring library for Flutter.