flutterwidgetclick

How to implement click propagation in Flutter Stack for widgets on the same position?


I’m working with a Stack in Flutter where I have two widgets (blue and yellow) placed at the exact same position and with the same size. The blue widget is at the bottom, and the yellow widget is on top of it.zwo widgets

I want the top yellow widget to handle the click events initially. However, I would like the yellow widget to have the option to decide whether it wants to handle the event or pass it down to the blue widget underneath it.

I want users to be able to click on either the blue or yellow triangle. However, if the user clicks on the blue triangle, which is underneath the yellow triangle, I am unable to trigger the onTap event of the blue widget.

How can I achieve this behavior? Specifically, I want the yellow widget to handle the click event first, but if it decides not to process the event, I want the event to be passed to the blue widget below it.

Any suggestions or best practices for implementing this?


Solution

  • Try this:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: const Home(),
        );
      }
    }
    
    class Home extends StatefulWidget {
      const Home({super.key});
    
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      bool _yellowHandlesTap = true;
      void _handleYellowTap() {
        setState(() {
          _yellowHandlesTap = !_yellowHandlesTap;
        });
    
        if (_yellowHandlesTap) {
          print('Yellow widget handled the tap');
        } else {
          print('Yellow widget passed the tap to Blue widget');
        }
      }
    
      void _handleBlueTap() {
        print('Blue widget handled the tap');
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: SizedBox(
              height: 200,
              width: 200,
              child: Stack(
                children: [
                  Align(
                    alignment: Alignment.centerRight,
                    child: ClipPath(
                      clipper: BlueClipper(),
                      child: GestureDetector(
                        onTap: _handleBlueTap,
                        child: Container(
                          height: 200,
                          width: 200,
                          color: Colors.blue,
                          child: CustomPaint(
                            painter: BlueTriangleBorderPainter(),
                          ),
                        ),
                      ),
                    ),
                  ),
                  Align(
                    alignment: Alignment.centerRight,
                    child: ClipPath(
                      clipper: YellowClipper(),
                      child: GestureDetector(
                        onTap: _handleYellowTap,
                        child: Container(
                          height: 200,
                          width: 200,
                          color: Colors.yellow,
                          child: CustomPaint(
                            painter: YellowTriangleBorderPainter(),
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class BlueClipper extends CustomClipper<Path> {
      @override
      Path getClip(Size size) {
        final x = size.width;
        final y = size.height;
        final path = Path();
    
        path
          ..moveTo(0, 0)
          ..lineTo(x, 0)
          ..lineTo(0, y);
        path.close();
    
        return path;
      }
    
      @override
      bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
        return false;
      }
    }
    
    class YellowClipper extends CustomClipper<Path> {
      @override
      Path getClip(Size size) {
        final x = size.width;
        final y = size.height;
        final path = Path();
        path
          ..moveTo(x, y)
          ..lineTo(x, 0)
          ..lineTo(0, y);
        path.close();
    
        return path;
      }
    
      @override
      bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
        return false;
      }
    }
    
    class BlueTriangleBorderPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = 3
          ..color = Colors.black;
    
        final path = Path();
    
        var y = size.height;
        var x = size.width;
    
        path
          ..moveTo(0, 0)
          ..lineTo(x, 0)
          ..lineTo(0, y);
    
        path.close();
    
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return false;
      }
    }
    
    class YellowTriangleBorderPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = 3
          ..color = Colors.black;
    
        final path = Path();
    
        var y = size.height;
        var x = size.width;
    
        path
          ..moveTo(x, y)
          ..lineTo(x, 0)
          ..lineTo(0, y);
    
        path.close();
    
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return false;
      }
    }