Skip to content

Instantly share code, notes, and snippets.

@Alexi-Zemcov
Forked from flutter-clutter/overlay_with_hole.dart
Last active March 3, 2023 07:53
Show Gist options
  • Save Alexi-Zemcov/ded0f2a2c7793596ed758a0cd01176eb to your computer and use it in GitHub Desktop.
Save Alexi-Zemcov/ded0f2a2c7793596ed758a0cd01176eb to your computer and use it in GitHub Desktop.
Flutter overlay with a hole
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