How to remove right margin from floating action button?

Viewed 48

I am implementing FAB as Expandable with little dark overlay when FAB is clicked. My problem is there is right margin in Scaffold and my overlay is not filling whole view.

How to remove that right margin?

Here is how it's looking:

enter image description here

Here is my Scaffold code:

return Scaffold(
        resizeToAvoidBottomInset: true,
        floatingActionButton: !isSearchBarVisible ? SizedBox.expand(
          child: ExpandableFab(
            //key: _key,
            distance: size.height * 0.09,
            children: [
              /* not needed to show problem */
            ],
          ),
        ) : null,
        body: /* some body */

and here is ExpandableFab class

class ExpandableFab extends StatefulWidget {
  const ExpandableFab({
    Key? key,
    this.initialOpen,
    required this.distance,
    required this.children,
  }) : super(key: key);

  final bool? initialOpen;
  final double distance;
  final List<Widget> children;

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

class _ExpandableFabState extends State<ExpandableFab>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  Animation<double>? _expandAnimation;
  bool _open = false;

  @override
  void initState() {
    super.initState();
    _open = widget.initialOpen ?? false;
    _controller = AnimationController(
      value: _open ? 1.0 : 0.0,
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _expandAnimation = CurvedAnimation(
      curve: Curves.fastOutSlowIn,
      reverseCurve: Curves.easeOutQuad,
      parent: _controller,
    );
  }

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

  void _toggle() {
    setState(() {
      _open = !_open;
      if (_open) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return GestureDetector(
      onTap: () => _toggle(),
      child: Stack(
        alignment: Alignment.bottomRight,
        clipBehavior: Clip.none,
        children: [
            IgnorePointer(
              ignoring: !_open,
              child: TweenAnimationBuilder<double>(
                tween: Tween<double>(begin: 0.0, end: _open ? 1.0 : 0.0),
                duration: Duration(milliseconds: 500),
                curve: Curves.easeInOut,
                builder: (_, value, child) {
                  if (value < 0.001) {
                    return child!;
                  }
                  return BackdropFilter(
                    filter: ImageFilter.blur(sigmaX: value, sigmaY: value),
                    child: child,
                  );
                },
                child: Container(color: Colors.transparent),
              ),
            ),
            IgnorePointer(
              ignoring: !_open,
              child: AnimatedOpacity(
                duration: Duration(milliseconds: 500),
                opacity: _open ? 1 : 0,
                curve: Curves.easeInOut,
                child: Container(
                  color: Colors.black12,
                ),
              ),
            ),
          Transform.translate(
            offset: Offset(0, 0),
            child: Stack(
              alignment: Alignment.bottomRight,
              children: [
                Positioned(
                    bottom: size.height * 0.14,
                    child: _buildTapToCloseFab(size)
                ),
                Positioned(
                    bottom: size.height * 0.14,
                    child: _buildTapToOpenFab(size)
                ),
                ..._buildExpandingActionButtons(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTapToCloseFab(Size size) {
    return SizedBox(
      width: 56.0,
      height: 56.0,
      child: Center(
        child: Material(
          shape: const CircleBorder(),
          clipBehavior: Clip.antiAlias,
          elevation: 4.0,
          child: InkWell(
            onTap: _toggle,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Icon(
                Icons.close,
                color: Theme.of(context).primaryColor,
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildTapToOpenFab(Size size) {
    return IgnorePointer(
      ignoring: _open,
      child: AnimatedContainer(
        transformAlignment: Alignment.center,
        transform: Matrix4.diagonal3Values(
          _open ? 0.7 : 1.0,
          _open ? 0.7 : 1.0,
          1.0,
        ),
        duration: const Duration(milliseconds: 250),
        curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
        child: AnimatedOpacity(
          opacity: _open ? 0.0 : 1.0,
          curve: const Interval(0.25, 1.0, curve: Curves.easeInOut),
          duration: const Duration(milliseconds: 250),
          child: FloatingActionButton(
            onPressed: _toggle,
            child: Icon(
                Icons.add,
            ),
          ),
        ),
      ),
    );
  }

  List<Widget> _buildExpandingActionButtons() {
    final children = <Widget>[];
    final count = widget.children.length;
    final step = 90.0 / (count - 1);
    var dist;
    for (var i = 0, angleInDegrees = 0.0;
        i < count;
        i++, angleInDegrees += step) {
      if (i == 0) {
        dist = (widget.distance) * (i + 1);
      }
      else {
        dist = (widget.distance) * (i + 1);
      }
      children.add(
        _ExpandingActionButton(
          directionInDegrees: 90,
          maxDistance: dist,
          progress: _expandAnimation,
          child: widget.children[i],
        ),
      );
    }
    return children;
  }
}

class _ExpandingActionButton extends StatelessWidget {
  const _ExpandingActionButton({
    Key? key,
    required this.directionInDegrees,
    required this.maxDistance,
    required this.progress,
    required this.child,
  }) : super(key: key);

  final double directionInDegrees;
  final double maxDistance;
  final Animation<double>? progress;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return AnimatedBuilder(
      animation: progress!,
      builder: (BuildContext context, Widget? child) {
        final offset = Offset.fromDirection(
          directionInDegrees * (math.pi / 180.0),
          progress!.value * maxDistance,
        );
        return Positioned(
          right: 4.0 + offset.dx,
          bottom: (size.height * 0.14) + 4.0 + offset.dy,
          child: Transform.rotate(
            angle: (1.0 - progress!.value) * math.pi / 2,
            child: child,
          ),
        );
      },
      child: FadeTransition(
        opacity: progress!,
        child: child,
      ),
    );
  }
}

It's mostly code from Flutter tutorial: https://docs.flutter.dev/cookbook/effects/expandable-fab with some little changes like vertical expand or overlay when FAB is expanded.

1 Answers

Use this structure:

    Scaffold(
    resizeToAvoidBottomInset: true,
    floatingActionButton: !isSearchBarVisible ? Padding(
      padding: const EdgeInsets.all(8.0),
      child: SizedBox.expand(
        child: ExpandableFab(
          //key: _key,
          distance: size.height * 0.09,
          children: [
            /* not needed to show problem */
          ],
        ),
      ),
    ) : null,
    body:
Related