Crafting responsive Desktop UI for OverlayEntry in Flutter

·

4 min read

In Flutter, OverlayEntry allows developers to create overlay widgets on top of existing UI elements. In this article, we will dive into the process of creating OverlayEntry, tackling the challenge of resizing window, and demonstrate its potential with a QR popup on NetShare. Let’s get started!

OverlayEntry overview

OverlayEntry renders widgets on top of existing UI elements, providing the ability to create interactive and visually appealing overlays. To create a simple OverlayEntry, we can use the following code snippet:

// Create an OverlayEntry
final overlayEntry = OverlayEntry(builder: (context) {}

// Add the OverlayEntry to the Overlay
Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);

The problem is that we don’t have a specific location for it yet. So how to display OverlayEntry at the desired position?

Assume that we need to show the OverlayEntry at the button position when it is clicked. First, find the position of the button with findRenderObject() from button key and then use localToGlobal() to convert the local position to the global coordinate system. Next, use these positions in the Positioned widget. And finally fill up content for OverlayEntry, which is the child of Positioned.

 _showOverlay() {
    _clearOverlay();

    final RenderBox buttonRenderBox = _buttonKey.currentContext?.findRenderObject() as RenderBox;
    final buttonSize = buttonRenderBox.size;
    final buttonPosition = buttonRenderBox.localToGlobal(Offset.zero);

    overlayEntry = OverlayEntry(builder: (context) {
      return GestureDetector(
          behavior: HitTestBehavior.translucent,
          onTap: _clearOverlay,
          child: Stack(
            children: [
              Positioned(
                top: buttonPosition.dy + buttonSize.height,
                left: buttonPosition.dx + buttonSize.height,
                child: SizedBox(
                  width: overlaySize,
                  height: overlaySize,
                  child: Container(
                    alignment: Alignment.center,
                    color: Colors.blue,
                    child: const Text(
                      'Hello',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                        decoration: TextDecoration.none,
                      ),
                    ),
                  ),
                ),
              )
            ],
          ));
    });

    // Add the OverlayEntry to the Overlay.
    Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);
  }

  _clearOverlay() {
    overlayEntry?.remove();
    overlayEntry = null;
  }

You may notice the snippet code above call to _clearOverlay() method. It’s just to make sure that only one OverlayEntry is shown with the action that triggered it.

And this is the result:

Responsive OverlayEntry on Desktop UI

Even OverlayEntry can be displayed, it lacks the flexibility to adapt window resizing.

Resizing a window should not hinder the seamless experience of our overlays. To ensure that our OverlayEntry adapts to changes in window size, we can leverage the power of LayoutBuilder. By wrapping our overlay content with a LayoutBuilder widget, we gain access to the constraints of the parent container.

  _showOverlay() {
    _clearOverlay();

    overlayEntry = OverlayEntry(builder: (context) {
      return GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTap: _clearOverlay,
        child: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            final RenderBox buttonRenderBox = _buttonKey.currentContext?.findRenderObject() as RenderBox;
            final buttonSize = buttonRenderBox.size;
            final buttonPosition = buttonRenderBox.localToGlobal(Offset.zero);
            return Stack(
              children: [
                Positioned(
                  top: buttonPosition.dy + buttonSize.height,
                  left: buttonPosition.dx + buttonSize.height,
                  child: SizedBox(
                    width: overlaySize,
                    height: overlaySize,
                    child: Container(
                      alignment: Alignment.center,
                      color: Colors.blue,
                      child: const Text(
                        'Hello',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 16.0,
                          decoration: TextDecoration.none,
                        ),
                      ),
                    ),
                  ),
                )
              ],
            );
          },
        ),
      );
    });

    // Add the OverlayEntry to the Overlay.
    Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);
  }

Explain more about this, why do we include the button position calculation in LayoutBuilder? That’s because when the window resizes, the position of the button is also changed, so it is necessary to recalculate its position when the window resizing ends. Let’s see how this worked:

That’s great, right? Let’s check the complete source code for this demo at this gist.

Bringing it All Together — QR Popup on NetShare

To demonstrate the versatility of OverlayEntry, let’s take a look at QR popup on NetShare. NetShare is an open-source Flutter project that makes it easy to share data in a local network. The latest release includes support for Android, macOS, Windows, and Linux platforms.

NetShare utilizes OverlayEntry to display a QR code overlay when the user clicks on the “QR” image button within the NetShare app in Server mode. The QR code overlay will provide a convenient way for users to quickly scan and access shared files on their mobile devices.

In this article, we explored the fundamentals of OverlayEntry, tackled the challenge of resizing window, and demonstrated its potential by creating a captivating QR popup on NetShare. Armed with this knowledge, you can now unleash your creativity and build visually stunning and interactive desktop applications with Flutter.

Happy Flutter-ing!

Stay in touch with me for updates: