Fab Circular Menu In Flutter

Fab Circular Menu In Flutter :

Screenshot :

Fab Circular Menu In Flutter

main.dart

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: FabCircularMenu(
          child: Container(
            color: Colors.indigo[900],
            child: Center(
                child: Padding(
              padding: const EdgeInsets.only(bottom: 256.0),
              child: Text('FAB Circle Menu',
                  textAlign: TextAlign.center,
                  style: TextStyle(color: Colors.white, fontSize: 36.0)),
            )),
          ),
          ringColor: Colors.white30,
          options: <Widget>[
            IconButton(
                icon: Icon(Icons.widgets),
                onPressed: () {},
                iconSize: 48.0,
                color: Colors.white),
            IconButton(
                icon: Icon(Icons.widgets),
                onPressed: () {},
                iconSize: 48.0,
                color: Colors.white),
            IconButton(
                icon: Icon(Icons.widgets),
                onPressed: () {},
                iconSize: 48.0,
                color: Colors.white),
            IconButton(
                icon: Icon(Icons.widgets),
                onPressed: () {},
                iconSize: 48.0,
                color: Colors.white),
          ],
        ),
      ),
    );
  }
}

fab_circular_menu.dart

library fab_circular_menu;

import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:vector_math/vector_math.dart' as vector;

class FabCircularMenu extends StatefulWidget {

  final Widget child;
  final List<Widget> options;
  final Color ringColor;
  final double ringDiameter;
  final double ringWidth;
  final EdgeInsets fabMargin;
  final Color fabColor;
  final Icon fabOpenIcon;
  final Icon fabCloseIcon;
  final Duration animationDuration;

  FabCircularMenu({
    @required this.child,
    @required this.options,
    this.ringColor = Colors.white,
    this.ringDiameter,
    this.ringWidth,
    this.fabMargin = const EdgeInsets.all(24.0),
    this.fabColor,
    this.fabOpenIcon = const Icon(Icons.menu),
    this.fabCloseIcon = const Icon(Icons.close),
    this.animationDuration = const Duration(milliseconds: 800)
  });

  @override
  _FabCircularMenuState createState() => _FabCircularMenuState();

}

class _FabCircularMenuState extends State<FabCircularMenu>
  with SingleTickerProviderStateMixin {

  double ringDiameter;
  double ringWidth;
  Color fabColor;

  bool animating = false;
  bool open = false;
  AnimationController controller;
  Animation<double> scaleAnimation;
  Animation scaleCurve;
  Animation<double> rotateAnimation;
  Animation rotateCurve;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    ringDiameter = widget.ringDiameter ?? MediaQuery.of(context).size.width * 1.2;
    ringWidth = widget.ringWidth ?? ringDiameter / 3;
    fabColor = widget.fabColor ?? Theme.of(context).primaryColor;

    controller = AnimationController(
        duration: widget.animationDuration,
        vsync: this
    );

    scaleCurve = CurvedAnimation(
        parent: controller, curve: Interval(0.0, 0.4, curve: Curves.easeInOutCirc)
    );
    scaleAnimation = Tween<double>(begin: 0.0, end: 1.0)
        .animate(scaleCurve)
      ..addListener(() {
        setState(() {});
      });

    rotateCurve = CurvedAnimation(
        parent: controller, curve: Interval(0.4, 1.0, curve: Curves.easeInOutCirc)
    );
    rotateAnimation = Tween<double>(begin: 1.0, end: 90.0)
        .animate(rotateCurve)
      ..addListener(() {
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    final double bottom = -(scaleAnimation.value * ringDiameter / 2 - 40.0 - (widget.fabMargin.bottom / 2));
    final double right = -(scaleAnimation.value * ringDiameter / 2 - 40.0 - (widget.fabMargin.right / 2));

    return Stack(
      alignment: Alignment.bottomRight,
      children: <Widget>[
        widget.child,

        Positioned(
          bottom: bottom,
          right: right,
          child: Container(
            width: scaleAnimation.value * ringDiameter,
            height: scaleAnimation.value * ringDiameter,
            child: CustomPaint(
              foregroundPainter: _RingPainter(
                ringColor: widget.ringColor,
                ringWidth: scaleAnimation.value * ringWidth
              ),
            ),
          ),
        ),

        Positioned(
          bottom: bottom,
          right: right,
            child: Container(
              width: scaleAnimation.value * ringDiameter,
              height: scaleAnimation.value * ringDiameter,
              child: Transform.rotate(
                angle: -(math.pi / rotateAnimation.value),
                child: Stack(
                  alignment: Alignment.center,
                  children: _applyTranslations(widget.options)
                ),
              ),
            ),
        ),

        Padding(
          padding: widget.fabMargin,
          child: FloatingActionButton(
            child: open ? widget.fabCloseIcon : widget.fabOpenIcon,
            backgroundColor: fabColor,
            onPressed: () {
              if (!animating && !open) {
                animating = true;
                open = true;
                controller.forward().then((_) {
                  animating = false;
                });
              } else if (!animating) {
                animating = true;
                open = false;
                controller.reverse().then((_) {
                  animating = false;
                });
              }
            }
          ),
        )
      ],
    );
  }

  List<Widget> _applyTranslations(List<Widget> widgets) {
    return widgets.asMap().map((index, widget) {
      final double angle = 90.0 / (widgets.length * 2 - 2) * (index * 2);
      return MapEntry(index, _applyTranslation(angle, widget));
    }).values.toList();
  }

  Widget _applyTranslation(double angle, Widget widget) {
    final double rad = vector.radians(angle);
    return Transform(
      transform: Matrix4.identity()
        ..translate(
            -(ringDiameter / 2) * math.cos(rad),
            -(ringDiameter / 2) * math.sin(rad)
        ),
      child: widget,
    );
  }

}


class _RingPainter extends CustomPainter {

  final Color ringColor;
  final double ringWidth;

  _RingPainter({
    @required this.ringColor,
    @required this.ringWidth
  });

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = ringColor
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke
      ..strokeWidth = ringWidth;

    Offset center = Offset(size.width / 2, size.height / 2);

    canvas.drawCircle(center, size.width / 2, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;

}

The flutter tutorial  is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application

For more information about Flutter. visit www.fluttertutorial.in