Dennis M
Dennis M

Reputation: 13

How to design a flutter semi circular menu with dynamic radius

Help, I am trying to create a semi circular menu like this, image of what I want here

But I dont know how to place the widgets on top of the larger white line...I have tried using the following code. Please use the widget as the home widget in your main.dart and see the result. I have used the font_awesome_flutter package in packages pub for the icons:-

image of how it is currently here

import 'package:flutter/material.dart';
import 'dart:math';
import 'package:vector_math/vector_math.dart' show radians, Vector3;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class Homewidgettoslack extends StatefulWidget {
  Homewidgettoslack({
    Key key,
  }) : super(key: key);

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

class _HomewidgettoslackState extends State<Homewidgettoslack> with TickerProviderStateMixin {
  Animation<double> rotation;
  Animation<double> translation;
  Animation<double> menuscale;
  AnimationController menuController;

  @override
  void initState() {
    super.initState();
    menuController =
        AnimationController(duration: Duration(milliseconds: 900), vsync: this);
    rotation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: menuController,
        curve: Interval(
          0.0,
          0.7,
          curve: Curves.decelerate,
        ),
      ),
    );
    translation = Tween<double>(
      begin: 0.0,
      end: 100.0,
    ).animate(
      CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
    );

    menuscale = Tween<double>(
      begin: 1.5,
      end: 0.0,
    ).animate(
      CurvedAnimation(parent: menuController, curve: Curves.fastOutSlowIn),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blueGrey,
      child: Scaffold(
        backgroundColor: Colors.transparent,
        body: Stack(
          children: <Widget>[
            Positioned(
              left: -27,
              right: -27,
              top: 57,
              bottom: 57,
              child: Container(
                height: MediaQuery.of(context).size.height,
                width: MediaQuery.of(context).size.width,
                child: CustomPaint(
                  painter: CurvedblacklinePainter(),
                ),
              ),
            ),
            Container(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: CustomPaint(
                painter: CurvedblacklinePainter(),
              ),
            ),
            Container(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: AnimatedBuilder(
                animation: menuController,
                builder: (context, widget) {
                  return Transform.rotate(
                    angle: radians(rotation.value),
                    child:
                        Stack(alignment: Alignment.center, children: <Widget>[
                      _buildButton(0,
                          color: Colors.red, icon: FontAwesomeIcons.thumbtack),
                      _buildButton(45,
                          color: Colors.green, icon: FontAwesomeIcons.sprayCan),
                      _buildButton(90,
                          color: Colors.orange, icon: FontAwesomeIcons.fire),
                      _buildButton(270,
                          color: Colors.pink, icon: FontAwesomeIcons.car),
                      _buildButton(315,
                          color: Colors.yellow, icon: FontAwesomeIcons.bolt),
                      Transform.scale(
                        scale: menuscale.value - 1,
                        child: FloatingActionButton(
                            child: Icon(FontAwesomeIcons.timesCircle),
                            onPressed: _close,
                            backgroundColor: Colors.red),
                      ),
                      Transform.scale(
                        scale: menuscale.value,
                        child: FloatingActionButton(
                            child: Icon(
                              FontAwesomeIcons.solidDotCircle,
                              color: Colors.red,
                            ),
                            onPressed: _open),
                      )
                    ]),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  _buildButton(double angle, {Color color, IconData icon}) {
    final double rad = radians(angle);
    return Transform(
        transform: Matrix4.identity()
          ..translate(
              (translation.value) * cos(rad), (translation.value) * sin(rad)),
        child: FloatingActionButton(
            child: Icon(icon),
            backgroundColor: color,
            onPressed: _close,
            elevation: 0));
  }

  _open() {
    print('OPEN CLICKED');
    menuController.forward();
  }

  _close() {
    print('CLOSE CLICKED');
    menuController.reverse();
  }
}

class CurvedblacklinePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = new Paint()
      ..color = Colors.white
      ..strokeWidth = 1.8
      ..style = PaintingStyle.stroke;

    Path path = Path();
    path.moveTo(0, 0);

    var secondEndPoint = Offset(0, size.height);
    path.arcToPoint(secondEndPoint,
        radius: Radius.circular((size.width / 2)),
        clockwise: true,
        largeArc: false);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

Upvotes: 1

Views: 1358

Answers (1)

Taleb
Taleb

Reputation: 2249

I improve your code and i fixed it as below ,

please replace my code with your code

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:vector_math/vector_math.dart' show radians, Vector3;

class Homewidgettoslack extends StatefulWidget {
  Homewidgettoslack({
    Key key,
  }) : super(key: key);

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

class _HomewidgettoslackState extends State<Homewidgettoslack>
    with TickerProviderStateMixin {
  Animation<double> rotation;
  Animation<double> translationY;
  Animation<double> translationX;
  Animation<double> menuscale;
  AnimationController menuController;

  double _menuIconSize;

  @override
  void initState() {
    super.initState();
    menuController =
        AnimationController(duration: Duration(milliseconds: 900), vsync: this);
    rotation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: menuController,
        curve: Interval(
          0.0,
          0.7,
          curve: Curves.decelerate,
        ),
      ),
    );

    menuscale = Tween<double>(
      begin: 1.5,
      end: 0.0,
    ).animate(
      CurvedAnimation(parent: menuController, curve: Curves.fastOutSlowIn),
    );
  }

  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(width: 360, height: 640, allowFontScaling: false);
    _menuIconSize = ScreenUtil().setWidth(60);
    double _bigCurveHeight = ScreenUtil().setHeight(520);
    double _bigCurveWidth = ScreenUtil().setWidth(250);

    translationY = Tween<double>(
      begin: 0.0,
      end: ((_bigCurveHeight / 2) - (_menuIconSize / 2)),
    ).animate(
      CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
    );

    translationX = Tween<double>(
      begin: 0.0,
      end: (_bigCurveWidth - (_menuIconSize / 2)),
    ).animate(
      CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
    );

    return Container(
      color: Colors.blueGrey,
      child: Scaffold(
        backgroundColor: Colors.transparent,
        body: Align(
          alignment: Alignment.centerLeft,
          child: Stack(
            alignment: Alignment.centerLeft,
            children: <Widget>[
              Container(
                height: _bigCurveHeight,
                width: _bigCurveWidth,
                child: CustomPaint(
                  painter: CurvedblacklinePainter(strokeWidth: 2),
                ),
              ),
              Container(
                height: ScreenUtil().setHeight(350),
                width: ScreenUtil().setWidth(130),
                child: CustomPaint(
                  painter: CurvedblacklinePainter(
                      strokeWidth: ScreenUtil().setWidth(60)),
                ),
              ),
              Container(
                child: AnimatedBuilder(
                  animation: menuController,
                  builder: (context, widget) {
                    return Transform.rotate(
                      angle: radians(rotation.value),
                      child:
                          Stack(alignment: Alignment.center, children: <Widget>[
                        _buildButton(0,
                            color: Colors.red,
                            icon: FontAwesomeIcons.thumbtack),
                        _buildButton(45,
                            color: Colors.green,
                            icon: FontAwesomeIcons.sprayCan),
                        _buildButton(90,
                            color: Colors.orange, icon: FontAwesomeIcons.fire),
                        _buildButton(270,
                            color: Colors.pink, icon: FontAwesomeIcons.car),
                        _buildButton(315,
                            color: Colors.yellow, icon: FontAwesomeIcons.bolt),
                        Transform.scale(
                          scale: menuscale.value - 1,
                          child: FloatingActionButton(
                              child: Icon(FontAwesomeIcons.timesCircle),
                              onPressed: _close,
                              backgroundColor: Colors.red),
                        ),
                        Transform.scale(
                          scale: menuscale.value,
                          child: FloatingActionButton(
                              child: Icon(
                                FontAwesomeIcons.solidDotCircle,
                                color: Colors.red,
                              ),
                              onPressed: _open),
                        )
                      ]),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  _buildButton(double angle, {Color color, IconData icon}) {
    final double rad = radians(angle);
    return Transform(
        transform: Matrix4.identity()
          ..translate(
              (translationX.value) * cos(rad), (translationY.value) * sin(rad)),
        child: FloatingActionButton(
            child: Icon(icon),
            backgroundColor: color,
            onPressed: _close,
            elevation: 0));
  }

  _open() {
    print('OPEN CLICKED');
    menuController.forward();
  }

  _close() {
    print('CLOSE CLICKED');
    menuController.reverse();
  }
}

class CurvedblacklinePainter extends CustomPainter {
  final double strokeWidth;

  CurvedblacklinePainter({@required this.strokeWidth});

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = new Paint()
      ..color = Colors.white
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke;

    canvas.drawOval(
        Rect.fromCenter(
            center: Offset(-size.width / 2, size.height / 2),
            width: size.width * 3,
            height: size.height),
        paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

and add dependency in your pubspec.yaml :

dev_dependencies:
  flutter_test:
    sdk: flutter
  font_awesome_flutter: ^8.8.1
  flutter_screenutil: ^2.1.0

result screenshot:

Upvotes: 2

Related Questions