I am developing an App using flutter and came across a custom curve like structure and I have no Idea how to create one. I am sharing a screenshot for the same.
I did some research and found that it might be possible with clip path widget but again I have no clue how will that work. I would also appreciate if someone also explained how to solve this.
A clean way to do this is using a ShapeBorder
, so you can use it on any container. Similar to the ClipPath
widget, you can define a path to use. The ShapeBorder
gets you the path from a bounding Rect
of this Container
. In the code below, I draw the top arc above this rect, so the top border aligns (and you can put other Widgets right above this container too).
import 'package:flutter/material.dart';
class WavyBorder extends ShapeBorder {
const WavyBorder({this.radius = 32});
final double radius;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
throw UnimplementedError();
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()
// The top curve is above this container's Rect, keep this in mind with your other Widgets
..moveTo(rect.topLeft.dx, rect.topLeft.dy - radius)
// The 90 degree arc at the top
..relativeArcToPoint(
Offset(radius, radius),
radius: Radius.circular(radius),
clockwise: false,
)
// The horizontal line
..lineTo(rect.topRight.dx - radius, rect.topRight.dy)
// The 90 degree arc at the bottom
..relativeArcToPoint(
Offset(radius, radius),
radius: Radius.circular(radius),
)
// Then just visit the bottom corners normally
..lineTo(rect.bottomRight.dx, rect.bottomRight.dy)
..lineTo(rect.bottomLeft.dx, rect.bottomLeft.dy)
..close();
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
@override
ShapeBorder scale(double t) {
throw UnimplementedError();
}
}
Then, wherever you want to use it, you can add it in the ShapeDecoration
of your Container
:
Container(
decoration: ShapeDecoration(
shape: WavyBorder(radius: 32),
...
),
child: ...,
),
Which looks like this:
Complete code for usage (main.dart
):
import 'package:flutter/material.dart';
import 'wavy_container.dart';
void main() {
runApp(const MaterialApp(
home: HomeScreen(),
debugShowCheckedModeBanner: false,
));
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController controller = TextEditingController();
final FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
controller.text = 'Hallo';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Flex(
direction: Axis.vertical,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Text(
'This text fits neatly above the container. Watch out with the left margin right above it though!',
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
),
),
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.all(8),
decoration: ShapeDecoration(
shape: const WavyBorder(radius: 32),
color: Theme.of(context).colorScheme.primary,
),
child: Text(
'Look at that pretty wavy border above here!',
style: Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(color: Colors.white),
textAlign: TextAlign.center,
),
),
)
],
),
);
}
}