Skip to content

Instantly share code, notes, and snippets.

@hungtrn75
Created October 17, 2020 17:20
Show Gist options
  • Save hungtrn75/eab33d6c0d7e7fb58ed58f7f000c0bc1 to your computer and use it in GitHub Desktop.
Save hungtrn75/eab33d6c0d7e7fb58ed58f7f000c0bc1 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
class Ticket extends StatelessWidget {
final double margin;
final double borderRadius;
final double clipRadius;
final double smallClipRadius;
final double ticketHeight;
final int numberOfSmallClips;
final Widget child;
final List<double> position;
const Ticket({
Key key,
this.margin = 20,
this.borderRadius = 10,
this.clipRadius = 12.5,
this.smallClipRadius = 5,
this.numberOfSmallClips = 13,
this.ticketHeight = 65,
this.child,
this.position = const [],
}) : super(key: key);
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
final ticketWidth = screenSize.width - margin * 2;
return Container(
width: ticketWidth,
// height: ticketHeight,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
offset: Offset(0, 8),
color: Colors.black.withOpacity(0.1),
blurRadius: 37,
spreadRadius: 0,
),
],
),
child: ClipPath(
clipper: TicketClipper(
borderRadius: borderRadius,
clipRadius: clipRadius,
smallClipRadius: smallClipRadius,
numberOfSmallClips: numberOfSmallClips,
position: position,
),
child: child,
),
);
}
}
class TicketClipper extends CustomClipper<Path> {
static const double clipPadding = 18;
final double borderRadius;
final double clipRadius;
final double smallClipRadius;
final int numberOfSmallClips;
final List<double> position;
const TicketClipper({
this.borderRadius,
this.clipRadius,
this.smallClipRadius,
this.position,
this.numberOfSmallClips,
});
@override
Path getClip(Size size) {
var path = Path();
final clipCenterY = size.height * 0.5;
// draw rect
path.addRRect(RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
Radius.circular(borderRadius),
));
// draw small clip circles
final clipContainerSize = size.width - clipRadius * 2 - clipPadding * 2;
final smallClipSize = smallClipRadius * 2;
final smallClipBoxSize = clipContainerSize / numberOfSmallClips;
final smallClipPadding = (smallClipBoxSize - smallClipSize) / 2;
final smallClipStart = clipRadius + clipPadding;
final clipPath = Path();
if (position.length == 0) {
// circle on the left
clipPath.addOval(Rect.fromCircle(
center: Offset(0, clipCenterY),
radius: clipRadius,
));
// circle on the right
clipPath.addOval(Rect.fromCircle(
center: Offset(size.width, clipCenterY),
radius: clipRadius,
));
final smallClipCenterOffsets = List.generate(numberOfSmallClips, (index) {
final boxX = smallClipStart + smallClipBoxSize * index;
final centerX = boxX + smallClipPadding + smallClipRadius;
return Offset(centerX, clipCenterY);
});
smallClipCenterOffsets.forEach((centerOffset) {
clipPath.addOval(Rect.fromCircle(
center: centerOffset,
radius: smallClipRadius,
));
});
} else {
position.forEach((pos) {
clipPath.addOval(Rect.fromCircle(
center: Offset(0, pos),
radius: clipRadius,
));
// circle on the right
clipPath.addOval(Rect.fromCircle(
center: Offset(size.width, pos),
radius: clipRadius,
));
final smallClipCenterOffsets =
List.generate(numberOfSmallClips, (index) {
final boxX = smallClipStart + smallClipBoxSize * index;
final centerX = boxX + smallClipPadding + smallClipRadius;
return Offset(centerX, pos);
});
smallClipCenterOffsets.forEach((centerOffset) {
clipPath.addOval(Rect.fromCircle(
center: centerOffset,
radius: smallClipRadius,
));
});
});
}
// combine two path together
final ticketPath = Path.combine(
PathOperation.reverseDifference,
clipPath,
path,
);
return ticketPath;
}
@override
bool shouldReclip(TicketClipper old) =>
old.borderRadius != borderRadius ||
old.clipRadius != clipRadius ||
old.smallClipRadius != smallClipRadius ||
old.position != position ||
old.numberOfSmallClips != numberOfSmallClips;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment