Flutter: How to achieve a Radial Widget like this in flutter? Circular breadcrumbs/ step progress indicator

Solution 1:

Use stack and Postioned widget :

calculate your area :

enter image description here

top right corner is Offset zero (x=0, y = 0), you can pass the calculated data to Positioned Widget

here the simple code for your reference :

Widget build(BuildContext context) {

double areaRadius = MediaQuery.of(context).size.width;
double bigCircleRadius = areaRadius * 0.8;
double smallCircleRadius = areaRadius * 0.15;
double imageRadius = areaRadius * 0.6;

return Scaffold(
  appBar: AppBar(
    automaticallyImplyLeading: true,
  ),
  body: SizedBox(
    height: areaRadius,
    width: areaRadius,
    child: Stack(
      children: [
        ///Bottom Layer --> BIG Circle
        Positioned(
          top: areaRadius * 0.1,
          left: areaRadius * 0.1,
          child: Container(
            width: bigCircleRadius,
            height: bigCircleRadius,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              border: Border.all(
                color: Colors.black.withOpacity(0.1),
                width: 4.0
              )
            ),
          ),
        ),
        Positioned(
          top: areaRadius * 0.2,
          left: areaRadius * 0.2,
          child: Container(
            width: imageRadius,
            height: imageRadius,
            decoration: const BoxDecoration(
                image: DecorationImage(
                  image: AssetImage("assets/images/burger.png"),
                  fit: BoxFit.contain
                ),
                shape: BoxShape.circle
            ),
          ),
        ),
        ///you can use trigonometry for this <---- Create a Offset(x,y) left is X and top is Y
        Positioned(
          left: (areaRadius * 0.5) - (smallCircleRadius /2),
          top: (areaRadius * 0.1) - (smallCircleRadius /2),
          child: Container(
            width: smallCircleRadius,
            height: smallCircleRadius,
            decoration: const BoxDecoration(
                color: Colors.blue,
                shape: BoxShape.circle
            ),
            child: const Center(
              child: Text("1", style: TextStyle(color: Colors.white, fontSize: 20.0),),
            ),
          ),
        ),

        Positioned(
          left: (areaRadius * 0.25) - (smallCircleRadius/2),
          top: (areaRadius * 0.8) - (smallCircleRadius /2),
          child: Container(
            width: smallCircleRadius,
            height: smallCircleRadius,
            decoration: const BoxDecoration(
                color: Colors.grey,
                shape: BoxShape.circle
            ),
            child: const Center(
              child: Text("2", style: TextStyle(color: Colors.black, fontSize: 20.0),),
            ),
          ),
        ),

        Positioned(
          left: (areaRadius * 0.75) - (smallCircleRadius/2),
          top: (areaRadius * 0.8) - (smallCircleRadius /2),
          child: Container(
            width: smallCircleRadius,
            height: smallCircleRadius,
            decoration: const BoxDecoration(
                color: Colors.grey,
                shape: BoxShape.circle
            ),
            child: const Center(
              child: Text("3", style: TextStyle(color: Colors.black, fontSize: 20.0),),
            ),
          ),
        ),
      ]
    ),
  ),
);

}

and here the result :

enter image description here

Can you comment my answer? I don't have notification for your response, for dynamic you can make a logic with radians of course, and for misalign on your widget that because you not calculated small circle size, you can use fractional offset from that.

Here is the code :

import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:sekolah_app/tema.dart';
class TestingPage extends StatefulWidget {
  const TestingPage({Key? key}) : super(key: key);

  @override
  _TestingPageState createState() => _TestingPageState();
}
class _TestingPageState extends State<TestingPage> {
  double areaRadius = 100;
  int count = 4;
  @override
  Widget build(BuildContext context) {
    var smallCircleRadius = areaRadius > 0 ? areaRadius*0.25  : 0.0;
    double anchorPointX = lebarLayar(context)*0.5 - (areaRadius/2);
    double anchorPointY = lebarLayar(context)*0.5 - (areaRadius/2);
    return Scaffold(
      body: Column(
        children: [
          Container(
            width: lebarLayar(context),
            height: lebarLayar(context),
            color: Colors.yellow.withOpacity(0.1),
            child: Stack(
              alignment: Alignment.center,
              children:
              _bigCircle(areaRadius, anchorPointX, anchorPointY) +
              _buildChildren(context, areaRadius, smallCircleRadius, count, anchorPointX, anchorPointY),
            ),
          ),
          _controller(value: areaRadius, onChanged: (radius){
            setState(() {
              areaRadius = radius;
            });
          }),
          _controller(value: count.toDouble(), min: 0, max: 8, onChanged: (itemCount){
            setState(() {
              count = itemCount.toInt();
            });
          })
        ],
      ),
    );
  }
  _buildChildren(context,aR,sCR,c, apX, apY) {
    final children = <Widget>[];
    var angle = 360 / c;
    var middlePoint = aR/2;
    for (var i = 1; i <= c; i++) {
      var r = aR / 2;
      var radians = (angle * math.pi / 180) * (i-1);
      double x = r * (math.sin(radians));
      double y = r * (math.cos(radians));
      debugPrint("x:${x.roundToDouble()}: ${y.roundToDouble()}");
      children.add(_customChild(
          sCR,
          middlePoint + x + apX,
          middlePoint - y + apY,
          i
      ));
    }
    return children;
  }
  _customChild(smallCircleRadius,x,y,i){
    return  Positioned(
      left: x,
      top: y,
      child: FractionalTranslation(
        translation: const Offset(-0.5,-0.5),
        child: Container(
          width: smallCircleRadius,
          height: smallCircleRadius,
          decoration: const BoxDecoration(
              color: Colors.cyan,
              shape: BoxShape.circle
          ),
          child:  Center(
            child: Text(i.toString(), style: TextStyle(color: Colors.white, fontSize: 20.0),),
          ),
        ),
      ),
    );
  }
  List<Widget>_bigCircle (double radius, apx, apy){
    return [
      Positioned(
        left: apx,
        top: apy,
        child: Container(
          height: radius,
          width: radius,
          decoration: const BoxDecoration(
            shape: BoxShape.circle,
            color: Colors.grey
          ),
        ),
      )
    ];
  }
  Widget _controller({required double value, required void Function(double)? onChanged, double? min, double? max}){
    return Slider(
        value: value,
        onChanged: onChanged,
        min: min??0.0,
        max: max??320,
    );
  }
}

the update result : enter image description here