Forked from flutter-clutter/overlay_with_hole.dart
Last active
March 3, 2023 07:53
-
-
Save Alexi-Zemcov/ded0f2a2c7793596ed758a0cd01176eb to your computer and use it in GitHub Desktop.
Flutter overlay with a hole
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.dark(), | |
home: Scaffold( | |
appBar: AppBar( | |
title: const Text( | |
'Flutterclutter: Holes', | |
style: TextStyle(color: Colors.black), | |
), | |
), | |
body: Stack( | |
children: [ | |
const _Content(), | |
Column( | |
children: const [ | |
Expanded( | |
child: ClipPathOverlay(), | |
), | |
Expanded( | |
child: CustomPaintOverlay(), | |
), | |
Expanded( | |
child: ColorFilteredOverlay(), | |
), | |
], | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class ClipPathOverlay extends StatelessWidget { | |
const ClipPathOverlay({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return ClipPath( | |
clipper: _InvertedClipper(), | |
child: const ColoredBox( | |
color: Colors.black54, | |
child: SizedBox.expand(), | |
), | |
); | |
} | |
} | |
class CustomPaintOverlay extends StatelessWidget { | |
const CustomPaintOverlay({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return CustomPaint( | |
size: Size.infinite, | |
painter: _HolePainter(), | |
); | |
} | |
} | |
class ColorFilteredOverlay extends StatelessWidget { | |
const ColorFilteredOverlay({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return ColorFiltered( | |
colorFilter: const ColorFilter.mode(Colors.black54, BlendMode.srcOut), | |
child: ColoredBox( | |
color: Colors.transparent, | |
child: Align( | |
alignment: Alignment.center, | |
child: Container( | |
margin: const EdgeInsets.only(right: 4, bottom: 4), | |
height: 100, | |
width: 200, | |
decoration: BoxDecoration( | |
// Color does not matter but must not be transparent | |
color: Colors.black, | |
borderRadius: BorderRadius.circular(16), | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
const _colors = [ | |
Colors.red, | |
Colors.orange, | |
Colors.yellow, | |
Colors.green, | |
Colors.lightBlue, | |
Colors.blue, | |
Colors.purple, | |
]; | |
class _Content extends StatelessWidget { | |
const _Content({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return GridView.builder( | |
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | |
crossAxisCount: 5, | |
), | |
itemBuilder: (_, i) => Container( | |
color: _colors[i % _colors.length], | |
), | |
); | |
} | |
} | |
class _HolePainter extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final centerSize = size / 2; | |
final bgPaint = Paint()..color = Colors.black54; | |
final theHolePath = Path() | |
..addRRect( | |
RRect.fromLTRBR( | |
centerSize.width - 100, | |
centerSize.height - 50, | |
centerSize.width + 100, | |
centerSize.height + 50, | |
const Radius.circular(16), | |
), | |
); | |
canvas.drawPath( | |
Path.combine( | |
PathOperation.difference, | |
Path() | |
..addRect( | |
Rect.fromLTWH( | |
0, | |
0, | |
size.width, | |
size.height, | |
), | |
), | |
theHolePath, | |
), | |
bgPaint, | |
); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return false; | |
} | |
} | |
class _InvertedClipper extends CustomClipper<Path> { | |
@override | |
Path getClip(Size size) { | |
final centerSize = size / 2; | |
final backgroundPath = Path() | |
..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); | |
final theHolePath = Path() | |
..addRRect( | |
RRect.fromLTRBR( | |
centerSize.width - 100, | |
centerSize.height - 50, | |
centerSize.width + 100, | |
centerSize.height + 50, | |
const Radius.circular(16), | |
), | |
); | |
return Path.combine( | |
PathOperation.difference, | |
backgroundPath, | |
theHolePath, | |
); | |
} | |
@override | |
bool shouldReclip(CustomClipper<Path> oldClipper) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment