'How to get a floating box when i click on a button in flutter

I want to make this type of UI -

enter image description here

enter image description here

Like this dialog opens when we click on "Other Details" and appears just below it. Is there any package for this? Else I will try to use stack and positioned, and will position it as accurate as I can.



Solution 1:[1]

You could use the native Flutter Widget https://api.flutter.dev/flutter/material/ExpansionPanel-class.html or try a package like https://pub.dev/packages/expandable

Solution 2:[2]

You could use an ExpansionTile. It does exactly what you are looking for.

Code sample:

Center(
    child: ExpansionTile(
      iconColor: Colors.red,
      title: const Text('More details'),
      children: [
        ListTile(
            title: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: const [
            Text('Name'),
            Text('Ankit'),
          ],
        )),
        ListTile(
            title: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: const [
            Text('Grade'),
            Text('9th'),
          ],
        )),
        ListTile(
            title: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: const [
            Text('Date of birth'),
            Text('21-04-2008'),
          ],
        )),
      ],
    ),
  )

screenshot_closed

screenshot_opened

Keep in mind that in the children you could put any widget, even a container, as:

container


EDIT

After reading your clarifying comment this is what I made: infoBox

Obviously it's a basic floating box, but you can customize it.

I used a Stack to overlap widgets and a boolean to check if you need to show or not the infobox.

bool visibile = false;

Center(
        child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
            onPressed: () => setState(() {
                  visibile = !visibile;
                }),
            icon: const Icon(Icons.remove_red_eye)),
        Stack(
          alignment: Alignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                children: const [
                  ElevatedButton(
                    onPressed: null,
                    child: Text("Check In"),
                  ),
                  TextField()
                ],
              ),
            ),
            visibile
                ? Container(
                    width: MediaQuery.of(context).size.width * .8,
                    height: MediaQuery.of(context).size.height * .5,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        boxShadow: [
                          BoxShadow(
                              offset: Offset.fromDirection(1, 2),
                              spreadRadius: 1,
                              blurRadius: 3,
                              color: Colors.grey)
                        ],
                        borderRadius: const BorderRadius.all(Radius.circular(20)),
                        border: Border.all(
                            color: Colors.orange,
                            width: 2,
                            style: BorderStyle.solid)),
                    child: Padding(
                      padding: const EdgeInsets.all(20.0),
                      child: SingleChildScrollView(
                        child: Column(
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: const [
                                Text(
                                  'Academic year',
                                  style: TextStyle(color: Colors.grey),
                                ),
                                Text('2023'),
                              ],
                            ),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: const [
                                Text(
                                  'Student name',
                                  style: TextStyle(color: Colors.grey),
                                ),
                                Text('Ankit'),
                              ],
                            ),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: const [
                                Text(
                                  'Grade',
                                  style: TextStyle(color: Colors.grey),
                                ),
                                Text('9th'),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ),
                  )
                : Container(),
          ],
        ),
      ],
    ))

Having the button outside the Stack prevents the InfoBox to hide it and makes it so you can click it at any time.


I have no idea why, but someone with enough privileges keeps deleting my comments on this answer.

Solution 3:[3]

If you Want the widget float on the other widgets, you can use OverlayEntry. There is a medium article/guide about this: Click to read.

Abstract:

first you need a global key, an overlay widget and the other variables below:

  GlobalKey overlayKey = LabeledGlobalKey("button_icon");
  late OverlayEntry overlayEntry;
  late Size buttonSize;
  late Offset buttonPosition;
  bool isMenuOpen = false;

then add the global key to your button's key proprty:

key: context.read<MainViewModel>().overlayKey,

then, find the clicked button using this function:

  findButton() {
    RenderBox renderBox =
        overlayKey.currentContext!.findRenderObject() as RenderBox;
    buttonSize = renderBox.size;
    buttonPosition = renderBox.localToGlobal(Offset.zero);
  }

then, a builder:

  OverlayEntry overlayEntryBuilder() {
    return OverlayEntry(
      builder: (context) {
        return Positioned(
          top: buttonPosition.dy + buttonSize.height * 4 / 5,
          left: buttonPosition.dx - (buttonSize.width / 2) - 32,

          // width: buttonSize.width,
          child: YourWidgetThatWillOpenWhenYouClickTheButton(),
        );
      },
    );
  }

you can play around with the values in the Positioned widget to get the overlay where you want it.

now you can call these functions to open/close the overlay:

  void openMenu(BuildContext context) {
    findButton();
    overlayEntry = overlayEntryBuilder();
    Overlay.of(context)!.insert(overlayEntry);
    isMenuOpen = !isMenuOpen;
  }

  void closeMenu() {
    overlayEntry.remove();
    isMenuOpen = !isMenuOpen;
  }

like this:

IconButton(
        key: context.read<MainViewModel>().overlayKey,
        onPressed: () {
          if (!context.read<MainViewModel>().isMenuOpen) {
            context.read<MainViewModel>().openMenu(context);
          } else {
            context.read<MainViewModel>().closeMenu();
          }
        },

check the medium article for the detailed explanation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 João Soares
Solution 2
Solution 3 Arash Mohammadi