flutterdart

Children that overflow their parents don't receive tap detection


i am facing an issue when i use a stack and add a child that has a gesture detector and position it outside the parent widget using positioned it doesn't detect gestures anymore , if there is a fix please explain it thank you . my code for the problem :

...List<Widget>.generate(
            images.length,
            (index) {
              return Stack(
                clipBehavior: Clip.none,
                children: [
                  Container(
                    height: deviceWidth / 3.5,
                    width: deviceWidth / 3.5,
                    clipBehavior: Clip.antiAlias,
                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(10)),
                    child: GestureDetector(
                      onLongPressStart: (details) {},
                      onTap: () async {
                        var imageFile = await pickImage();
                        if (imageFile != null) {
                          images[index] = makeImage(
                              isPlaceholder: false,
                              imageData: await imageFile.readAsBytes());
                          setState(
                            () {},
                          );
                        }
                      },
                      child: images[index],
                    ),
                  ),
                  Positioned(
                    left: -10,
                    top: -15,
                    child: Container(
                      alignment: Alignment.center,
                      height: 30,
                      width: 30,
                      decoration: const ShapeDecoration(
                          shape: CircleBorder(), color: Colors.red),
                      child: InkWell(
                        splashColor: Colors.white,
                        onTap: () {
                          images.remove(images[index]);
                          setState(() {});
                        },
                        child: const IgnorePointer(
                            child: Icon(Icons.delete_forever)),
                      ),
                    ),
                  )
                ],
              );
            },
          ),

Solution

  • This behaviour is intentional. The framework hitTest goes from parent to children, and by default widget don't allow child to receive hitTest when it is outside of the parent's box.

    To achieve "outside of box" gesture detection of an overflowed child. You could push the child that would overflow to the Overlay with OverlayPortal and position it with LayerLink.

    import 'package:flutter/material.dart';
    
    main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('LayerLink'),
            ),
            body: const Center(child: Home()),
          ),
        );
      }
    }
    
    class Home extends StatelessWidget {
      const Home({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Padding(
          padding: EdgeInsets.all(20.0),
          child: Wrap(
            spacing: 10.0,
            runSpacing: 10.0,
            children: [
              MyImage(color: Colors.blue),
              MyImage(color: Colors.green),
              MyImage(color: Colors.yellow),
              MyImage(color: Colors.orange),
              MyImage(color: Colors.purple),
              MyImage(color: Colors.red),
              MyImage(color: Colors.pink),
              MyImage(color: Colors.teal),
              MyImage(color: Colors.brown),
              MyImage(color: Colors.grey),
            ],
          ),
        );
      }
    }
    
    class MyImage extends StatefulWidget {
      const MyImage({super.key, required this.color});
      final Color color;
    
      @override
      State<MyImage> createState() => _MyImageState();
    }
    
    class _MyImageState extends State<MyImage> {
      int count = 0;
      final controller = OverlayPortalController();
      final link = LayerLink();
    
      @override
      void initState() {
        super.initState();
        controller.show();
      }
    
      @override
      Widget build(BuildContext context) {
        return OverlayPortal(
          controller: controller,
          child: CompositedTransformTarget(
            link: link,
            child: Container(
              color: widget.color,
              height: 100,
              width: 100,
              alignment: Alignment.center,
              child: Text(
                "$count",
                style: const TextStyle(fontSize: 30),
              ),
            ),
          ),
          overlayChildBuilder: (context) {
            return Center(
              child: CompositedTransformFollower(
                link: link,
                followerAnchor: Alignment.center,
                showWhenUnlinked: false,
                child: Container(
                  alignment: Alignment.center,
                  height: 30,
                  width: 30,
                  decoration: const ShapeDecoration(shape: CircleBorder(), color: Colors.red),
                  child: InkWell(
                    splashColor: Colors.white,
                    onTap: () {
                      setState(() {
                        count++;
                      });
                    },
                    child: const IgnorePointer(child: Icon(Icons.delete_forever)),
                  ),
                ),
              ),
            );
          },
        );
      }
    }