Created
March 5, 2024 17:22
-
-
Save Plumillon/8effb10db0af11fb927baa04284ef7f5 to your computer and use it in GitHub Desktop.
TabPanel part 1
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
/// TabPanel part 1 | |
/// This is the first part of the TabPanel series: | |
/// https://medium.com/@FlavienNorindr/building-a-custom-vertical-tabbed-panel-widget-tabpanel-part-1-7fa7a44f6467 | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'TabPanel part 1', | |
theme: ThemeData( | |
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
useMaterial3: true, | |
), | |
home: const TabPanelPage(), | |
); | |
} | |
} | |
class TabPanelPage extends StatelessWidget { | |
const TabPanelPage({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
backgroundColor: const Color(0xFFFED700), | |
body: Center( | |
child: Stack( | |
alignment: Alignment.center, | |
children: [ | |
const FlutterLogo(size: 400), | |
ShadowClipPath( | |
shadow: BoxShadow( | |
color: Colors.black.withAlpha(200), | |
blurRadius: 10, | |
offset: Offset.zero, | |
spreadRadius: 0, | |
blurStyle: BlurStyle.outer), | |
clipper: TabPanelClipper(tabSize: const Size(90, 90), radius: 10), | |
child: Container( | |
width: 270, | |
height: 270, | |
color: Colors.green.withOpacity(0.8), | |
), | |
), | |
], | |
)), | |
); | |
} | |
} | |
class TabPanelClipper extends CustomClipper<Path> { | |
final Size tabSize; | |
final double radius; | |
TabPanelClipper({ | |
super.reclip, | |
required this.tabSize, | |
required this.radius, | |
}); | |
@override | |
Path getClip(Size size) { | |
return Path() | |
..moveTo(tabSize.width + radius, 0) | |
..quadraticBezierTo(tabSize.width, 0, tabSize.width, radius) | |
..lineTo(tabSize.width, tabSize.height - radius) | |
..quadraticBezierTo( | |
tabSize.width, tabSize.height, tabSize.width - radius, tabSize.height) | |
..lineTo(radius, tabSize.height) | |
..quadraticBezierTo(0, tabSize.height, 0, tabSize.height + radius) | |
..lineTo(0, tabSize.height * 2 - radius) | |
..quadraticBezierTo(0, tabSize.height * 2, radius, tabSize.height * 2) | |
..lineTo(tabSize.width - radius, tabSize.height * 2) | |
..quadraticBezierTo(tabSize.width, tabSize.height * 2, tabSize.width, | |
tabSize.height * 2 + radius) | |
..lineTo(tabSize.width, tabSize.height * 3 - radius) | |
..quadraticBezierTo(tabSize.width, tabSize.height * 3, | |
tabSize.width + radius, tabSize.height * 3) | |
..lineTo(size.width - radius, tabSize.height * 3) | |
..quadraticBezierTo(size.width, tabSize.height * 3, size.width, | |
tabSize.height * 3 - radius) | |
..lineTo(size.width, radius) | |
..quadraticBezierTo(size.width, 0, size.width - radius, 0) | |
..close(); | |
} | |
@override | |
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true; | |
} | |
class ShadowClipPath extends StatelessWidget { | |
final Shadow shadow; | |
final CustomClipper<Path> clipper; | |
final Widget child; | |
final Clip clipBehavior; | |
const ShadowClipPath({ | |
super.key, | |
required this.shadow, | |
required this.clipper, | |
required this.child, | |
this.clipBehavior = Clip.antiAlias, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return CustomPaint( | |
painter: ShadowClipPainter( | |
clipper: clipper, | |
shadow: shadow, | |
), | |
child: ClipPath( | |
clipper: clipper, | |
clipBehavior: clipBehavior, | |
child: child, | |
), | |
); | |
} | |
} | |
class ShadowClipPainter extends CustomPainter { | |
final Shadow shadow; | |
final CustomClipper<Path> clipper; | |
ShadowClipPainter({required this.shadow, required this.clipper}); | |
@override | |
void paint(Canvas canvas, Size size) { | |
var paint = shadow.toPaint(); | |
var clipPath = clipper.getClip(size).shift(shadow.offset); | |
canvas.drawPath(clipPath, paint); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment