Last active
July 13, 2024 07:21
-
-
Save PlugFox/9ab286842c7fab41dd8181a9f3a8461b to your computer and use it in GitHub Desktop.
Custom Clipper Ticket with Circular Cutouts (Serrator)
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
/* | |
* Custom Clipper Ticket with Circular Cutouts (Serrator) | |
* https://gist.github.com/PlugFox/9ab286842c7fab41dd8181a9f3a8461b | |
* https://dartpad.dev?id=9ab286842c7fab41dd8181a9f3a8461b | |
* Mike Matiunin <plugfox@gmail.com>, 13 July 2024 | |
*/ | |
import 'package:flutter/material.dart'; | |
void main() => runApp( | |
MaterialApp( | |
home: Scaffold( | |
body: const SafeArea( | |
child: Padding( | |
padding: EdgeInsets.all(16.0), | |
child: Center( | |
child: Ticket(), | |
), | |
), | |
), | |
), | |
), | |
); | |
class Ticket extends StatelessWidget { | |
const Ticket({ | |
super.key, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
final theme = Theme.of(context); | |
return FittedBox( | |
fit: BoxFit.scaleDown, | |
child: SizedBox( | |
width: 420, | |
height: 240, | |
child: ClipPath( | |
clipper: TicketClipper( | |
radius: 8, | |
padding: 4, | |
margin: const EdgeInsets.fromLTRB( | |
8 + 16, // To cover left card margin and radius | |
8, // To cover top card margin | |
8 + 16, // To cover right card margin and radius | |
8, | |
), | |
), | |
child: ShaderMask( | |
shaderCallback: (Rect bounds) => const LinearGradient( | |
begin: Alignment.topCenter, | |
end: Alignment.bottomCenter, | |
colors: <Color>[ | |
Colors.transparent, | |
Colors.transparent, | |
Colors.black87, | |
], | |
stops: <double>[.0, .5, 1.0], | |
).createShader(bounds), | |
blendMode: BlendMode.srcATop, | |
child: Card( | |
margin: const EdgeInsets.all(8), | |
color: theme.primaryColor, | |
elevation: 4, | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(16), | |
), | |
child: Padding( | |
padding: const EdgeInsets.fromLTRB(16, 32, 16, 8), | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
mainAxisAlignment: MainAxisAlignment.start, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
// ... | |
Text( | |
('Ticket with Circular Cutouts\n' * 6) | |
.trim() | |
.toUpperCase(), | |
style: theme.textTheme.titleLarge?.copyWith( | |
color: theme.colorScheme.onPrimary, | |
fontWeight: FontWeight.w900, | |
fontStyle: FontStyle.italic, | |
), | |
), | |
// ... | |
], | |
), | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class TicketClipper extends CustomClipper<Path> { | |
TicketClipper({ | |
this.radius = 12, | |
this.padding = 6, | |
this.margin = EdgeInsets.zero, | |
}); | |
/// Radius of the circular cutouts | |
final double radius; | |
/// Padding is the space between the circles | |
final double padding; | |
/// Margin is the space between the clipper and the card edges | |
final EdgeInsets margin; | |
@override | |
Path getClip(Size size) { | |
final diameter = radius * 2; | |
final path = Path()..lineTo(margin.left, 0); | |
final serratorWidth = size.width - margin.horizontal; | |
final circles = serratorWidth ~/ (diameter + this.padding); | |
final padding = (serratorWidth - circles * diameter) / (circles + 1); | |
path.lineTo(margin.left + padding, margin.top); | |
for (var i = 0, x = margin.left + padding; | |
i < circles; | |
i++, x += diameter + padding) { | |
path | |
..arcToPoint( | |
Offset(x + diameter, margin.top), | |
radius: Radius.circular(radius), | |
clockwise: false, | |
) | |
..lineTo(x + diameter + padding, margin.top); | |
} | |
return path | |
..lineTo(size.width - margin.right, 0) | |
..lineTo(size.width, 0) | |
..lineTo(size.width, size.height) | |
..lineTo(0, size.height) | |
..lineTo(0, 0) | |
..close(); | |
} | |
@override | |
bool shouldReclip(CustomClipper<Path> oldClipper) => | |
oldClipper is! TicketClipper || | |
oldClipper.radius != radius || | |
oldClipper.padding != padding || | |
oldClipper.margin != margin; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment