Last active
September 9, 2024 08:28
-
-
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!
This file contains hidden or 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(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