Skip to content

Instantly share code, notes, and snippets.

@amalChandran
Last active September 9, 2024 08:28
Show Gist options
  • Save amalChandran/d8a4c8aa96b8dd7b1602303f8d5cc78b to your computer and use it in GitHub Desktop.
Save amalChandran/d8a4c8aa96b8dd7b1602303f8d5cc78b to your computer and use it in GitHub Desktop.
A card view which supports ridges on one of its any four sides. When combined with another card, this will look like a tearable ticket!
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Rounded Ridged Card Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rounded Ridged Card Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RoundedRidgedCard(
width: 200,
height: 200,
color: Colors.red,
topRidge: true,
bottomRidge: false,
child: const Center(child: Text('Top Ridge')),
),
RoundedRidgedCard(
width: 200,
height: 200,
color: Colors.green,
topRidge: false,
bottomRidge: true,
child: const Center(child: Text('Bottom Ridge')),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RoundedRidgedCard(
width: 200,
height: 200,
color: Colors.blue,
leftRidge: true,
rightRidge: false,
child: const Center(child: Text('Left Ridge')),
),
RoundedRidgedCard(
width: 200,
height: 200,
color: Colors.orange,
leftRidge: false,
rightRidge: true,
child: const Center(child: Text('Right Ridge')),
),
],
),
],
),
),
);
}
}
class RoundedRidgedCard extends StatelessWidget {
final Widget child;
final double width;
final double height;
final Color color;
final bool topRidge;
final bool bottomRidge;
final bool leftRidge;
final bool rightRidge;
final double ridgeDepth;
final double ridgeWidth;
final int ridgeCount;
final double borderRadius;
const RoundedRidgedCard({
Key? key,
required this.child,
required this.width,
required this.height,
this.color = Colors.blue,
this.topRidge = false,
this.bottomRidge = false,
this.leftRidge = false,
this.rightRidge = false,
this.ridgeDepth = 10,
this.ridgeWidth = 20,
this.ridgeCount = 4,
this.borderRadius = 16,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _RoundedRidgedCardPainter(
color: color,
topRidge: topRidge,
bottomRidge: bottomRidge,
leftRidge: leftRidge,
rightRidge: rightRidge,
ridgeDepth: ridgeDepth,
ridgeWidth: ridgeWidth,
ridgeCount: ridgeCount,
borderRadius: borderRadius,
),
child: Container(
width: width,
height: height,
padding: EdgeInsets.only(
top: topRidge ? ridgeDepth : borderRadius,
bottom: bottomRidge ? ridgeDepth : borderRadius,
left: leftRidge ? ridgeDepth : borderRadius,
right: rightRidge ? ridgeDepth : borderRadius,
),
child: child,
),
);
}
}
class _RoundedRidgedCardPainter extends CustomPainter {
final Color color;
final bool topRidge;
final bool bottomRidge;
final bool leftRidge;
final bool rightRidge;
final double ridgeDepth;
final double ridgeWidth;
final int ridgeCount;
final double borderRadius;
_RoundedRidgedCardPainter({
required this.color,
required this.topRidge,
required this.bottomRidge,
required this.leftRidge,
required this.rightRidge,
required this.ridgeDepth,
required this.ridgeWidth,
required this.ridgeCount,
required this.borderRadius,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final path = Path();
// Top left corner
path.moveTo(0, borderRadius);
path.quadraticBezierTo(0, 0, borderRadius, 0);
if (topRidge) {
_addHorizontalRidgeToPath(path, size, true);
} else {
path.lineTo(size.width - borderRadius, 0);
}
// Top right corner
path.quadraticBezierTo(size.width, 0, size.width, borderRadius);
if (rightRidge) {
_addVerticalRidgeToPath(path, size, true);
} else {
path.lineTo(size.width, size.height - borderRadius);
}
// Bottom right corner
path.quadraticBezierTo(
size.width, size.height, size.width - borderRadius, size.height);
if (bottomRidge) {
_addHorizontalRidgeToPath(path, size, false);
} else {
path.lineTo(borderRadius, size.height);
}
// Bottom left corner
path.quadraticBezierTo(0, size.height, 0, size.height - borderRadius);
if (leftRidge) {
_addVerticalRidgeToPath(path, size, false);
} else {
path.lineTo(0, borderRadius);
}
path.close();
canvas.drawPath(path, paint);
}
void _addHorizontalRidgeToPath(Path path, Size size, bool isTop) {
final effectiveWidth = size.width - 2 * borderRadius;
final totalWidth = ridgeWidth * ridgeCount * 2 - ridgeWidth;
final startX = borderRadius + (effectiveWidth - totalWidth) / 2;
for (int i = 0; i < ridgeCount; i++) {
final x1 = startX + (i * ridgeWidth * 2);
final x2 = x1 + ridgeWidth;
if (isTop) {
path.lineTo(x1, 0);
path.lineTo(x1, ridgeDepth);
path.lineTo(x2, ridgeDepth);
path.lineTo(x2, 0);
} else {
path.lineTo(x2, size.height);
path.lineTo(x2, size.height - ridgeDepth);
path.lineTo(x1, size.height - ridgeDepth);
path.lineTo(x1, size.height);
}
}
if (isTop) {
path.lineTo(size.width - borderRadius, 0);
} else {
path.lineTo(borderRadius, size.height);
}
}
void _addVerticalRidgeToPath(Path path, Size size, bool isRight) {
final effectiveHeight = size.height - 2 * borderRadius;
final totalHeight = ridgeWidth * ridgeCount * 2 - ridgeWidth;
final startY = borderRadius + (effectiveHeight - totalHeight) / 2;
for (int i = 0; i < ridgeCount; i++) {
final y1 = startY + (i * ridgeWidth * 2);
final y2 = y1 + ridgeWidth;
if (isRight) {
path.lineTo(size.width, y1);
path.lineTo(size.width - ridgeDepth, y1);
path.lineTo(size.width - ridgeDepth, y2);
path.lineTo(size.width, y2);
} else {
path.lineTo(0, y2);
path.lineTo(ridgeDepth, y2);
path.lineTo(ridgeDepth, y1);
path.lineTo(0, y1);
}
}
if (isRight) {
path.lineTo(size.width, size.height - borderRadius);
} else {
path.lineTo(0, borderRadius);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment