skip to Main Content

So I have this stack:

Stack(
  children:[
    Widget1(),
    Widget2(),
    Widget3(),
  ]);

they’re all full screen so you can only see Widget3

But I want to ‘see through’ these widgets to the one beneath at a certain area. Like maybe a circle of radius 100 in the center of the page or something: have a transparent area of the app.

And I’d love it if I could add in like a widget in the stack to accomplish this instead of wrapping the Widget, though that would work too:

Stack(
  children:[
    Widget1(),
    Widget2(),
    TransparentCenterArea(radius:50), // see through Widget2, to see Widget1
    Widget3(),
    TransparentCenterArea(radius:100), // see through Widget3, to see Widget2
  ]);

is something like this even possible? I can’t figure out a way to do it. Especially since I might want to change the radius, so as to have like an ‘opening’ animation or something…

Now, as a contrived example I have this, trying to use a CustomPainter but it really doesn’t work, I only see Widget3

import 'package:flutter/material.dart';

class TransparentCenterArea extends StatelessWidget {
  final double radius;

  TransparentCenterArea({required this.radius});

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(radius),
      child: CustomPaint(
        size: Size.fromRadius(radius),
        painter: TransparentPainter(),
      ),
    );
  }
}

class TransparentPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Draw a transparent circle in the center of the widget
    final paint = Paint()..color = Colors.transparent;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), size.width / 2, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('Transparent Center Area Example'),
      ),
      body: Stack(
        children: [
          Widget1(),
          Widget2(),
          TransparentCenterArea(radius: 50), // see through Widget2, to see Widget1
          Widget3(),
          TransparentCenterArea(radius: 100), // see through Widget3, to see Widget2
        ],
      ),
    ),
  ));
}

class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Center(child: Text('Widget 1')),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Center(child: Text('Widget 2')),
    );
  }
}

class Widget3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      child: Center(child: Text('Widget 3')),
    );
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    @RandalSchwartz had it right in his comment. I was able to come up with this from his advice:

    class AnimatedOpening extends StatefulWidget {
      final double initialRadius;
      final double finalRadius;
      final Duration animationDuration;
      final Widget? child;
    
      AnimatedOpening({
        required this.initialRadius,
        required this.finalRadius,
        required this.animationDuration,
        this.child,
      });
    
      @override
      _AnimatedOpeningState createState() => _AnimatedOpeningState();
    }
    
    class _AnimatedOpeningState extends State<AnimatedOpening>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      late Animation<double> _animation;
    
      @override
      void initState() {
        super.initState();
    
        _controller = AnimationController(
          duration: widget.animationDuration,
          vsync: this,
        );
    
        _animation = Tween<double>(
          begin: widget.initialRadius,
          end: widget.finalRadius,
        ).animate(_controller);
    
        _controller.forward();
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) => AnimatedBuilder(
            animation: _animation,
            builder: (context, child) => TransparentCenterArea(
              radius: _animation.value,
              child: child,
            ),
            child: widget.child,
          );
    }
    
    class TransparentCenterArea extends StatelessWidget {
      final double radius;
      final Widget? child;
      TransparentCenterArea({required this.radius, this.child});
    
      @override
      Widget build(BuildContext context) => ClipPath(
            clipper: TransparentClipper(radius: radius),
            child: child,
          );
    }
    
    class TransparentClipper extends CustomClipper<Path> {
      final double radius;
      TransparentClipper({required this.radius});
    
      @override
      Path getClip(Size size) => Path()
        ..addRect(Rect.fromPoints(Offset(0, 0), Offset(size.width, size.height)))
        ..addOval(Rect.fromCircle(
            center: Offset(size.width / 2, size.height / 2), radius: radius))
        ..fillType = PathFillType.evenOdd;
    
      @override
      bool shouldReclip(CustomClipper<Path> oldClipper) => true;
    }
    
    class Home extends StatelessWidget {
      const Home({super.key});
    
      @override
      Widget build(BuildContext context) => Stack(
            children: [
              Container(color: Colors.blue),
              AnimatedOpening(
                initialRadius: 0,
                finalRadius: 100,
                animationDuration: Duration(seconds: 2),
                child: Container(color: Colors.green),
              ),
              TransparentCenterArea(
                radius: 100,
                child: Container(color: Colors.orange),
              ),
            ],
          );
    }
    

  2. In Flutter, constraints goes down, size goes up and parent position the child. Now for your scenario, Every colored widget retuning container on Stack. and the container taking full space of the stack. You can provide a fixed height on each Container or use Positioned widget for this. Also while adding child, it is necessary wrap with any positioned widget like Positioned, Align widget for your case.

    First make sure to add a color on paint

       final paint = Paint()..color = Colors.red;//
    

    Here is the demo

    void main() {
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text('Transparent Center Area Example'),
          ),
          body: Stack(
            children: [
              Positioned.fill(
                //fill the whole screen
                child: Widget1(),
              ),
              Positioned(
                top: 100,
                left: 100,
                right: 100,
                bottom: 100,
                child: Widget2(),
              ),
              Positioned(
                right: 100,
                top: 100,
                child: TransparentCenterArea(radius: 50),
              ), // see through Widget2, to see Widget1
              Positioned(
                top: 100,
                left: 100,
                child: Widget3(),
              ), //have fixed size on Container
    
              Align(
                alignment: Alignment(0, -.5),
                child: TransparentCenterArea(radius: 100),
              ), // see through Widget3, to see Widget2
            ],
          ),
        ),
      ));
    }
    
    

    Widget will be paint from top to bottom, where TransparentCenterArea(radius: 100), will be render over Widget3 and so on.

    Find more about using Stack widget.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search