Search code examples
flutterbuttonstackoverlap

Flutter Button Stack - Icon needs to overlap parent


I have a button that requires the icon to be placed slightly outside the rectangle.

I can do this fine with a Stack, however the button border is overlapping the icon, as you can see here:

screenshot of button

It should look like this: edited image of button

here is my code:

OutlinedButton(
                style: scansButtonStyle,
                onPressed: () {}, // TODO: add later
                child: Stack(clipBehavior: Clip.none, children: [
                  Padding(
                    padding: const EdgeInsets.only(left: 50.0),
                    child: Text('CONNECT'),
                  ),
                  Positioned(
                    bottom: 0,
                    left: -20,
                    child: CircleAvatar(
                      radius: 30,
                      backgroundColor: Colors.black,
                      child: CircleAvatar(
                        radius: 27,
                        backgroundColor: Colors.white,
                        child: Icon(
                          Icons.bluetooth_connected,
                          color: Colors.black,
                          size: 48,
                        ),
                      ),
                    ),
                  ),
                ]),
              ),

and styling:

final ButtonStyle scansButtonStyle = OutlinedButton.styleFrom(
    alignment: Alignment.centerLeft,
    primary: Colors.black,
    backgroundColor: Color(0xfffcd722),
    minimumSize: Size(242, 48),
    padding: EdgeInsets.fromLTRB(20, 3, 20, 3),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.only(
          topLeft: Radius.circular(16),
          topRight: Radius.zero,
          bottomRight: Radius.circular(16),
          bottomLeft: Radius.zero),
    ),
    textStyle: const TextStyle(
      color: Colors.black,
      fontWeight: FontWeight.w600,
      fontSize: 34,
      fontFamily: 'HP',
    ),
    side: BorderSide(
      color: Colors.black,
      width: 3.0,
      style: BorderStyle.solid,
    ));

Solution

  • I've corrected the code a bit.
    Main changes are:

    1. Put the Outlined button inside the Stack
    2. Changed CircleAvatar to a simple Container with decoration (do you really need CircleAvatar for this? Will you animate the icon in any way? If not, then a simple Container might be better)
    3. Add IgnorePointer to the icon so that it ignores the tap event and passes it lower to the button itself (that's for the ripple effect even when pressed on the icon)
    4. Wrap Icon with the FittedBox widget and add paddings to the outer container. This will allow our Icon to dynamically change it's size depending on the size of the outer circle.
    5. Added some parameters to the Text widget so that it doesn't break the design if the text is longer than the width. You can check it with Text('CONNECT' * 10)
    Stack(
                      clipBehavior: Clip.none,
                      children: [
                        OutlinedButton(
                          onPressed: () {},
                          style: scansButtonStyle,
                          child: Padding(
                            padding: const EdgeInsets.only(left: 50.0),
                            child: Text(
                              'CONNECT',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              textScaleFactor: 1.0, // We do not want OS preferences (text scale option) to break our button's design
                            ),
                          ),
                        ),
                        Positioned(
                          bottom: 0,
                          child: IgnorePointer(
                            //ignore touch events, so that the Outlined button triggers touch animation when pressed on the icon
                            child: SizedBox(
                              width: 60.0,
                              child: AspectRatio(
                                aspectRatio: 1.0,
                                child: Container(
                                  padding: EdgeInsets.all(6.0),
                                  decoration: BoxDecoration(
                                    color: Colors.white,
                                    border: Border.all(
                                      color: Colors.black,
                                      width: 3.0,
                                    ),
                                    shape: BoxShape.circle,
                                  ),
                                  child: FittedBox(
                                    child: Icon(
                                      Icons.bluetooth_connected,
                                      color: Colors.black,
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    )
    

    The code might need some further adjustments, but you will get the idea.

    Please also note that the icon's top notch does not take any space (because of Clip.none and it's position in the Stack). You can test it by wrapping the button inside a colored Container.

    If you do not like the idea of AspectRatio inside a SizedBox - change it back to width/height in the Container.