Flutter see through widget in Stack

118 Views Asked by At

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

There are 2 best solutions below

0
Md. Yeasin Sheikh On

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.

0
MetaStack On

@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),
          ),
        ],
      );
}