An alternative to Overlay which allows you to easily render and hit test a widget outside its parent bounds

Overview

An alternative to Overlay which allows you to easily render and hit test a widget outside its parent bounds. Based on the original idea by @shrouxm here: https://github.com/flutter/flutter/issues/75747#issuecomment-907755810

Typically in Flutter, if you offset a widget outside of it's parent bounds hit-testing will break. DeferPointer works around this issue by handing off hit-testing and (optionally) rendering to an DeferredPointerHandler widget further up the tree.

While Overlay can solve this issue to some degree, using DeferPointer offers some benefits:

  • just works: no error prone and tedious layer management
  • more granular: you can set the bounds to be any ancestor widget you choose
  • more flexible: you can choose to paint the child on top, or not
  • easier to align/pin to a widget in the tree as that is the default expectation

This is useful for larger UI components like dropdown menus and sliding panels, as well as just small general styling tweaks.

🔨 Installation

dependencies:
  defer_pointer: ^0.0.2

Import

import 'package:defer_pointer/defer_pointer.dart';

🕹️ Usage

  1. Wrap a DeferredPointerHandler somewhere above the buttons that you wish to hit-test.
  2. Wrap DeferPointer around the buttons themselves.
Widget build(BuildContext context) {
    return DeferredPointerHandler(
       child: SizedBox(
           width: 100,
           height: 100,
           child: Stack(clipBehavior: Clip.none, children: [
             // Hang button off the bottom of the content
             Positioned(
               bottom: -30,
               child: DeferPointer(child: _SomeBtn(false)),
             ),
             // Content
             Positioned.fill(
               child: Container(
                 decoration: BoxDecoration(color: Colors.green, boxShadow: [
                   BoxShadow(color: Colors.black.withOpacity(1), blurRadius: 4, spreadRadius: 4),
                 ]),
               ),
             ),
           ]))));
  }

Enable paintOnTop if you need the child Widget painted on top of it's siblings. This will defer painting to the currently linked DeferredPointerHandler.

return DeferPointer(
    paintOnTop: true,
    child: TextButton(...));

Examples

There are 4 examples in this repo:

  1. A simple example of offsetting 2 buttons outside their stack: https://github.com/gskinnerTeam/flutter-defer-pointer/blob/master/example/lib/examples/simple_offset_outside_parent.dart

  2. A classic desktop/web style dropdown menu: https://github.com/gskinnerTeam/flutter-defer-pointer/blob/master/example/lib/examples/dropdown_menus.dart

  3. A animated menu based on the Flow widget: https://github.com/gskinnerTeam/flutter-defer-pointer/blob/master/example/lib/examples/flow_menu.dart

  4. A auto-complete search field: https://github.com/gskinnerTeam/flutter-defer-pointer/blob/master/example/lib/examples/auto_complete.dart

Manual Linking

By default a DeferPointer widget will look up the closest DeferredPointerHandler using it's current context. For more complicated use cases you can manually assign a link to bind a pointer to a handler:

final _deferredPointerLink = DeferredPointerHandlerLink();
...
Widget build(){
    return DeferredPointerHandler(
      link: _deferredPointerLink,
      child: Padding(
          padding: const EdgeInsets.all(20),
          child: DeferPointer(
            link: _deferredPointerLink,
            child: ...,
          )),
    );
}

🐞 Bugs/Requests

If you encounter any problems please open an issue. If you feel the library is missing a feature, please raise a ticket on Github and we'll look into it. Pull request are welcome.

📃 License

MIT License

You might also like...

A Flutter package to show beautiful animated snackbars directly using overlay

A Flutter package to show beautiful animated snackbars directly using overlay

Easily show beautiful snack bars directly using overlays. Create custom snack bars and show them with awesome animations.

Dec 27, 2022

A Flutter plugin that makes it easier to make floating/overlay windows for Android with pure Flutter

A Flutter plugin that makes it easier to make floating/overlay windows for Android with pure Flutter

flutter_floatwing A Flutter plugin that makes it easier to make floating/overlay windows for Android with pure Flutter. Android only Features Pure Flu

Dec 21, 2022

May be used to intercept the Android back-button, as an alternative to `WillPopScope`.

back_button_interceptor In simple cases, when you need to intercept the Android back-button, you usually add WillPopScope to your widget tree. However

Dec 12, 2022

A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate

A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate

A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.

Dec 13, 2022

A dice game made with Flutter and its Animation Widget

A dice game made with Flutter and its Animation Widget

Jogo de dado feito com animação em flutter Esse projeto é um jogo de dado feito com Flutter e seus Widget de animação. Não foi necessário uso de pacot

May 10, 2022

AdvFAB - An Advanced floating action button that expands itself to reveal its hidden widget

AdvFAB (More Than Just A Floating Action Button) AdvFAB is An Advanced floating action button that expands itself to reveal its hidden widget. It can

Nov 4, 2022

The flutter_otp_text_field package for flutter is a TextField widget that allows you to display different style pin.

The flutter_otp_text_field package for flutter is a TextField widget that allows you to display different style pin.

flutter_otp_text_field flutter_otp_text_field The flutter_otp_text_field package for flutter is a TextField widget that allows you to display differen

Nov 8, 2022

Responsive Widgets Prefix allows you to add the "Responsive" prefix to any widget that needs scaling or font size increases

Responsive Widgets Prefix allows you to add the

Responsive Widgets Prefix allows you to add the Responsive prefix to any widget that needs scaling or font size increases (for varying device screen sizes).

Apr 18, 2022

Flutter package which helps you to implement Ticket Widget in your app.

Flutter package which helps you to implement Ticket Widget in your app.

✨ Ticket Widget Flutter package which helps you to implement Ticket Widget in your app. The source code is 100% Dart, and it is an updated null safe v

Dec 30, 2022
Comments
  • Installation failed

    Installation failed

    Hi, when running the command: flutter pub add defer_pointer

    I get the following error: Unable to resolve package "defer_pointer" with the given git parameters

    When running flutter doctor -v, here is the output:

        • Flutter version 3.3.6 on channel stable at C:\Users\provo\Documents\flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 6928314d50 (5 days ago), 2022-10-25 16:34:41 -0400
        • Engine revision 3ad69d7be3
        • Dart version 2.18.2
        • DevTools version 2.15.0
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
        • Android SDK at C:\Users\provo\AppData\Local\Android\sdk
        • Platform android-32, build-tools 32.0.0
        • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
        • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)
        • All Android licenses accepted.
    
    [✓] Chrome - develop for the web
        • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe
    
    [✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.10.4)
        • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
        • Visual Studio Community 2019 version 16.10.31515.178
        • Windows 10 SDK version 10.0.19041.0
    
    [✓] Android Studio (version 2021.1)
        • Android Studio at C:\Program Files\Android\Android Studio
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)
    
    [✓] VS Code (version 1.72.2)
        • VS Code at C:\Users\provo\AppData\Local\Programs\Microsoft VS Code
        • Flutter extension version 3.50.0
    
    [✓] Connected device (3 available)
        • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.19044.2130]
        • Chrome (web)      • chrome  • web-javascript • Google Chrome 106.0.5249.119
        • Edge (web)        • edge    • web-javascript • Microsoft Edge 106.0.1370.34
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    • No issues found!
    opened by MarcAnProv 1
  • Does not work with `Flow.unwrapped`

    Does not work with `Flow.unwrapped`

    Hi! I am trying to make this package work with the Flow.unwrapped constructor, but I can't succeed.

    I am using this constructor because if I don't the buttons don't seem to move.

    I'll happily share some videos and minimal code to reproduce when #2 is solved, as it's blocking my interface so the videos wouldn't be very insightful...

    opened by raulmabe 0
  • Unexpected hit priority on `Flow`'s `children` property

    Unexpected hit priority on `Flow`'s `children` property

    Hi, I am trying to implement several FABs through the Flow widget.

    Context

    What defers my case from your example is that I want to have the button that expands/shrinks the Flowfrom now on menu button– is at position 0 of the children property, thus keeping it immobile. For this reason, my FlowDelegate paints the children from last to first; so when all buttons are shrunk, my menu button is painted above the others.

    This video section may clear out my intentions.

    Problem

    My problem starts when using this package. When all widgets are shrunk, the pointer event triggers the last button, not my menu button, thus not working properly.

    Expected output

    Trigger the menu button (GradientFAB) onPressed property, as its painted last and above the others.

    Actual output

    Triggers 'Second extra button', while being painted under the menu button.

    Files

    Widget file
    import 'dart:developer';
    import 'dart:math' as math;
    import 'dart:ui';
    
    import 'package:defer_pointer/defer_pointer.dart';
    import 'package:flutter/material.dart';
    
    class AnimatedFloatingActionButton {
      const AnimatedFloatingActionButton({
        required this.text,
        required this.icon,
        this.onPressed,
      });
      final String text;
      final IconData icon;
      final VoidCallback? onPressed;
    }
    
    const kPadding = 8.0;
    const fabSize = 56.0;
    
    class AnimatedFloatingActionButtons extends StatefulWidget {
      const AnimatedFloatingActionButtons({
        Key? key,
        required this.buttons,
      }) : super(key: key);
    
      final List<AnimatedFloatingActionButton> buttons;
    
      @override
      _AnimatedFloatingActionButtonsState createState() =>
          _AnimatedFloatingActionButtonsState();
    }
    
    class _AnimatedFloatingActionButtonsState
        extends State<AnimatedFloatingActionButtons>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      late CurvedAnimation _curvedAnimation;
    
      @override
      void initState() {
        super.initState();
    
        _controller = AnimationController(
          duration: 300.ms,
          vsync: this,
        );
        _curvedAnimation =
            CurvedAnimation(parent: _controller, curve: Curves.decelerate);
      }
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          height: fabSize,
          width: fabSize,
          child: DeferPointer(
            paintOnTop: true,
            child: Flow(
              clipBehavior: Clip.none,
              delegate: _MyFlowDelegate(
                animation: _curvedAnimation,
              ),
              children: [
                DeferPointer(
                  // paintOnTop: true,
                  child: GradientFAB(
                    onPressed: _toggleAnimation,
                    child: AnimatedBuilder(
                      animation: _curvedAnimation,
                      builder: (ctx, child) => Transform.rotate(
                        angle: lerpDouble(
                          0,
                          (90 + 45) * (math.pi / 180),
                          _controller.value,
                        )!,
                        child: child,
                      ),
                      child: const Icon(
                        Icons.add,
                        color: Colors.white,
                        size: 40.0,
                      ),
                    ),
                  ),
                ),
                ...widget.buttons
                    .map(buildButton)
                    .map((e) => DeferPointer(child: e))
                    .toList(),
              ],
            ),
          ),
        );
      }
    
      Widget buildButton(AnimatedFloatingActionButton button) {
        return AnimatedBuilder(
          animation: _curvedAnimation,
          builder: (context, child) {
            return FloatingActionButton(
              isExtended: true,
              elevation: lerpDouble(0, 6.0, _controller.value),
              heroTag: button.text,
              child: child!,
              backgroundColor: context.colorScheme.primaryVariant,
              onPressed: () => log(button.text), //button.onPressed,
            );
          },
          child: Icon(button.icon),
        );
      }
    
      void _toggleAnimation() {
        if (_controller.status == AnimationStatus.completed) {
          _controller.reverse();
        } else {
          _controller.forward();
        }
      }
    
      @override
      void dispose() {
        _controller.dispose();
        _curvedAnimation.dispose();
        super.dispose();
      }
    }
    
    class _MyFlowDelegate extends FlowDelegate {
      const _MyFlowDelegate({
        required this.animation,
      });
      final Animation animation;
    
      @override
      void paintChildren(FlowPaintingContext context) {
        final size = context.size;
        final xStart = size.width - fabSize;
        final yStart = size.height - fabSize;
    
        for (var i = context.childCount - 1; i >= 0; --i) {
          final offset = i * animation.value * (fabSize + kPadding);
          context.paintChild(
            i,
            transform: Matrix4.translationValues(
              xStart,
              yStart - offset,
              0,
            ),
          );
        }
      }
    
      @override
      bool shouldRepaint(covariant _MyFlowDelegate oldDelegate) {
        return animation != oldDelegate.animation;
      }
    }
    
    
    Example of use
               AnimatedFloatingActionButtons(
                    buttons: [
                      AnimatedFloatingActionButton(
                        text: 'First extra button',
                        icon: Icons.add_a_photo,
                        onPressed: () {},
                      ),
                      AnimatedFloatingActionButton(
                        text: 'Second extra button',
                        icon: Icons.add_alarm,
                        onPressed: () {},
                      ),
                    ],
                  ),
    
    opened by raulmabe 0
  • Not working with padding arround

    Not working with padding arround

     return Padding(
          padding: const EdgeInsets.only(top: 16),
          child: DeferredPointerHandler(
            child: SizedBox(
              width: 100,
              height: 100,
              child: Stack(
                clipBehavior: Clip.none,
                children: [
                  // Hang button off the bottom of the content
                  Positioned(
                    bottom: -30,
                    child: DeferPointer(child: TextButton(onPressed: () => print("lol"), child: Text("lol"))),
                  ),
                  // Content
                  Positioned.fill(
                    child: Container(
                      decoration: BoxDecoration(color: Colors.green, boxShadow: [
                        BoxShadow(color: Colors.black.withOpacity(1), blurRadius: 4, spreadRadius: 4),
                      ]),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
    

    image

    The lol button is not clickable outside the green block.

    opened by EArminjon 1
Owner
gskinner team
We collaborate with smart, motivated clients to conceptualize, design, and build world-class interactive experiences.
gskinner team
The SpannableGrid is a Flutter widget that allows its cells to span columns and rows and supports moving cells inside the grid.

Spannable Grid The SpannableGrid is a Flutter widget that allows its cells to span columns and rows and supports moving cells inside the grid. Feature

Evgeny Cherkasov 17 Nov 7, 2022
Flutter widget to show text in popup or overlay container

flutter_show_more_text_popup Flutter widget to show text in popup or overlay container Installation Add this to your package's pubspec.yaml file depen

Sanjay Sharma 44 Jul 5, 2022
A high performance Flutter Widget to render Bottts svg avatars on android/ios devices.

Flutter Avatars - Bottts A high performance Flutter Widget to render Bottts svg avatars on android/ios devices. (pub.dev) It's faster than other class

Abhijat Saxena 11 Dec 19, 2021
🟥 A flutter widget that flashes when flutter fails to render a frame in a certain timeframe

?? A flutter widget that flashes when flutter fails to render a frame in a certain timeframe

Andrei Lesnitsky 32 Oct 8, 2022
A widget which implicitly launches a hero animation when its position changed within the same route.

local_hero A widget which implicitly launches a hero animation when its position changed within the same route. Getting started In the pubspec.yaml of

Romain Rastel 174 Jan 6, 2023
Flutter Package: When your desired layout or animation is too complex for Columns and Rows, this widget lets you position/size/rotate/transform its child in complex ways.

align_positioned Widgets in this package: AlignPositioned AnimatedAlignPositioned AnimChain Why are these widgets an indispensable tool? When your des

Marcelo Glasberg 69 Dec 12, 2022
Provider support for overlay, make it easy to build toast and In-App notification.

overlay_support Provider support for overlay, make it easy to build toast and In-App notification. this library support ALL platform Interaction If yo

Bin 340 Jan 1, 2023
SmartSelect allows you to easily convert your usual form select or dropdown into dynamic page

SmartSelect allows you to easily convert your usual form select or dropdown into dynamic page, popup dialog, or sliding bottom sheet with various choices input such as radio, checkbox, switch, chips, or even custom input. Supports single and multiple choice.

Irfan Vigma Taufik 332 Dec 20, 2022
A basic flutter loading overlay

A basic loading overlay Features Creates a new scope where the user cannot leave until you programmatically pop it. Usage import 'package:flutter/mate

null 0 Nov 8, 2021
Flutter overlay loading dialog example

flutter_overlay_loading_dialog_example Demo

Javeed Ishaq 4 Mar 24, 2022