Draw Bezier curve with rectangle in Flutter - flutter

I'm starting with Flutter and have to design a UI that looks like
But with icon button at the center of the Bezier curve.
What I tried is
class HeaderPainter extends CustomPainter {
HeaderPainter({
#required this.color,
#required this.avatarRadius
});
final Color color;
final double avatarRadius;
#override
void paint(Canvas canvas, Size size) {
final shapeBounds = Rect.fromLTRB(0, 0, size.width, size.height - avatarRadius);
final centerAvatar = Offset(shapeBounds.center.dx, shapeBounds.bottom);
final avatarBounds = Rect.fromCircle(center: centerAvatar, radius: avatarRadius).inflate(3);
_drawBackground(canvas, shapeBounds, avatarBounds);
}
#override
bool shouldRepaint(HeaderPainter oldDelegate) {
return color != oldDelegate.color;
}
void _drawBackground(Canvas canvas, Rect shapeBounds, Rect avatarBounds) {
final paint = Paint()..color = color;
final backgroundPath = Path()
..moveTo(shapeBounds.left, shapeBounds.top)
..lineTo(shapeBounds.bottomLeft.dx, shapeBounds.bottomLeft.dy)
..arcTo(avatarBounds, -pi, pi, false)
..lineTo(shapeBounds.bottomRight.dx, shapeBounds.bottomRight.dy)
..lineTo(shapeBounds.topRight.dx, shapeBounds.topRight.dy)
..lineTo(0.0, shapeBounds.height - 100)
..quadraticBezierTo(
shapeBounds.width / 4, shapeBounds.height,
shapeBounds.width / 2, shapeBounds.height
)
..quadraticBezierTo(
shapeBounds.width - shapeBounds.width / 4, shapeBounds.height,
shapeBounds.width, shapeBounds.height - 100
)
..lineTo(shapeBounds.width, 0.0)
..close();
canvas.drawPath(backgroundPath, paint);
}
}
And the outcome is
How can I get the bezier curve with the rectangle?
Edit 2: The HeaderPainter is used like
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
child: CustomPaint(
size: Size.fromHeight(400.0),
painter: HeaderPainter(
color: Colors.red,
avatarRadius: avatarRadius
),
),
),
Positioned(
left: 0,
right: 0,
bottom: titleBottomMargin,
child: Column(
children: <Widget>[
Text('Hello World', style: TextStyle(fontWeight: FontWeight.bold),),
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: CircleAvatar(
radius: avatarRadius,
backgroundColor: Colors.green,
child: IconButton(icon: Icon(Icons.message), onPressed: _onAddMessageButtonClick,),
),
)
],
);
}

Got it solved using ClipPath and CustomClipper.
The updated build() method is
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
child: ClipPath(
clipper: HeaderClipper(avatarRadius: avatarRadius),
child: CustomPaint(
size: Size.fromHeight(400.0),
painter: HeaderPainter(
color: Colors.green,
avatarRadius: avatarRadius
),
),
),
),
...
and HeaderClipper
class HeaderClipper extends CustomClipper<Path> {
HeaderClipper({
#required this.avatarRadius
});
final avatarRadius;
#override
getClip(Size size) {
final path = Path()
..lineTo(0.0, size.height - 100)
..quadraticBezierTo(
size.width / 4, (size.height - avatarRadius),
size.width / 2, (size.height - avatarRadius)
)
..quadraticBezierTo(
size.width - (size.width / 4), (size.height - avatarRadius),
size.width, size.height - 100
)
..lineTo(size.width, 0.0)
..close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
The final output is

Related

Flutter border around container with text in the middle (divider?)

I try to create a border around a container, that is not that difficult ofcourse, but i need also a text IN the border with space around it. Like horizontal divder but i need a complete border.
Attached what i like to achieve.
Any one who can help me how to approach this?
Thanks!
Tried to use the horizontal and vertical divider packages, but then the border is not in full.
You can use CustomPainter like this:
class CustomDraw extends CustomPainter {
late Paint painter;
late double radius;
late double textWidth;
CustomDraw(Color color, this.textWidth, {this.radius = 0}) {
painter = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = color;
}
#override
void paint(Canvas canvas, Size size) {
var path = Path();
path.moveTo(size.width - ((size.width - textWidth) / 2), 0);
path.lineTo(size.width - radius, 0);
path.cubicTo(size.width - radius, 0, size.width, 0, size.width, radius);
path.lineTo(size.width, size.height - radius);
path.cubicTo(size.width, size.height - radius, size.width, size.height,
size.width - radius, size.height);
path.lineTo(radius, size.height);
path.cubicTo(radius, size.height, 0, size.height, 0, size.height - radius);
path.lineTo(0, radius);
path.cubicTo(0, radius, 0, 0, radius, 0);
path.lineTo(((size.width - textWidth) / 2), 0);
canvas.drawPath(path, painter);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
and this widget:
class CustomTitleWidget extends StatefulWidget {
final double height;
final double width;
final double? radius;
final String title;
const CustomTitleWidget(
{Key? key,
required this.height,
required this.width,
required this.title,
this.radius})
: super(key: key);
#override
State<CustomTitleWidget> createState() => _CustomTitleWidgetState();
}
class _CustomTitleWidgetState extends State<CustomTitleWidget> {
GlobalKey textKey = GlobalKey();
double textHeight = 0.0;
double textWidth = 0.0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
final textKeyContext = textKey.currentContext;
if (textKeyContext != null) {
final box = textKeyContext.findRenderObject() as RenderBox;
textHeight = box.size.height;
textWidth = box.size.width;
}
});
});
}
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
CustomPaint(
child: Container(
height: widget.height,
width: widget.width,
),
painter: CustomDraw(
Colors.red,
textWidth,
radius: widget.radius ?? 0,
),
),
Positioned(
top: -textHeight / 2,
child: Padding(
key: textKey,
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
widget.title,
),
),
)
],
);
}
}
use like this:
CustomTitleWidget(
height: 200,
width: double.infinity,
title: 'asdasdasdasdasd',
radius: 16),
#Maenda, We can implement such kind of structure using Stack, take one container with a border & put the other container over the first one.
Here is an example:
Stack(
children: [
Positioned(
top: 12,
left: 0,
right: 0,
child: Container(
height: 120,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(width: 1.2),
borderRadius: BorderRadius.circular(3)),
)),
Container(
margin: EdgeInsets.symmetric(
horizontal: 21,
),
child: Column(
children: [
Container(
width: 100,
margin: EdgeInsets.only(top: 12, bottom: 5),
alignment: Alignment.center,
color: Colors.blue,
child: Text(
"Any Text",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
Text(
"Nature, in the broadest sense, is the physical world or universe. Nature can refer to the phenomena of the physical world, and also to life in general.",
textAlign: TextAlign.center,
),
SizedBox(
height: 8,
)
],
),
),
],
);
Hope this works! You can customize as per the design.

How to make a cutout in a container using ClipPath?

I have a page on which the container is located, I need to make borders and a cutout for the buttons in this container. I want to do this using ClipPath when I added this widget, nothing has changed, there are no borders and there is no vyzer that I registered. Tell me why nothing is displayed, what is my mistake?
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: CustomClipperArc(position: 150, holeRadius: 32),
child: Container(
decoration: const BoxDecoration(
color: constants.Colors.greyXDark,
),
padding: const EdgeInsets.only(top: 50),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
constants.Assets.likeBoarding,
height: 50,
),
],
),
],
),
),
);
}
CustomClipperArc
class CustomClipperArc extends CustomClipper<Path> {
CustomClipperArc({this.position, this.holeRadius = 16});
double? position;
final double holeRadius;
#override
Path getClip(Size size) {
final path = Path()
..moveTo(0, 0)
..lineTo(position! - holeRadius, 0.0)
..lineTo(size.width, 0.0)
..lineTo(size.width, size.height)
..lineTo(position!, size.height)
..arcToPoint(
Offset(position! - holeRadius, size.height),
clockwise: false,
radius: const Radius.circular(1),
);
path.lineTo(0.0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) => oldClipper != this;
}
Here is your Clip Code... and also use Shape Maker to design such layout and you will get clip code
Your clip container with border
Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: <Widget>[
ClipPath(
clipper: CustomClipPathTopContainer(),
child: Container(
height: 400,
color: Colors.green,
),
),
CustomPaint(
painter: BorderPainter(),
child: Container(
height: 400,
),
),
],
),
),
Clipping Code of Container
import 'package:flutter/cupertino.dart';
class CustomClipPathTopContainer extends CustomClipper<Path> {
#override
Path getClip(Size size) {
double w = size.width;
double h = size.height;
Path path0 = Path();
path0.moveTo(0,size.height);
path0.lineTo(0,0);
path0.lineTo(size.width,0);
path0.lineTo(size.width,size.height);
path0.lineTo(size.width*0.3333333,size.height);
path0.lineTo(size.width*0.3014833,size.height*0.9182714);
path0.quadraticBezierTo(size.width*0.2795000,size.height*0.8617714,size.width*0.2488333,size.height*0.8552714);
path0.quadraticBezierTo(size.width*0.2145667,size.height*0.8643571,size.width*0.1948583,size.height*0.9179714);
path0.lineTo(size.width*0.1653417,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.close();
return path0;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Making Border Painting
class BorderPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = Colors.pink;
Path path0 = Path();
path0.moveTo(0,size.height);
path0.lineTo(0,size.height*0.2114714);
path0.quadraticBezierTo(size.width*0.1255833,size.height*0.4277857,size.width*0.2698833,size.height*0.4275143);
path0.cubicTo(size.width*0.4168083,size.height*0.4270286,size.width*0.5467250,size.height*0.0190429,size.width*0.6867167,size.height*0.0189000);
path0.quadraticBezierTo(size.width*0.8293000,size.height*0.0211000,size.width,size.height*0.2121429);
path0.lineTo(size.width,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height*0.9994143);
path0.close();
canvas.drawPath(path0, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

Flutter: CurveClipper with Container

I'm trying to curve a container using this code:
class CurveClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
int curveHeight = 40;
Offset controlPoint = Offset(size.width / 2, size.height + curveHeight);
Offset endPoint = Offset(size.width, size.height - curveHeight);
Path path = Path()
..lineTo(0, size.height - curveHeight)
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy)
..lineTo(size.width, 0)
..close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Usage:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// The title text which will be shown on the action bar
title: Text(title),
),
body: Container(
child: ClipPath(
clipper: CurveClipper(),
child: Container(
color: Colors.red,
height: 200.0,
),
),
),
);
}
However, this gets me this:
Screenshot
But I want the curve to be at the top of the container, not the bottom. How can I achieve this?
This one work for me
ClipPath File
Container(
margin: EdgeInsets.only(left: 15, right: 15),
alignment: Alignment.center,
child: ClipPath(
clipper: ClipPathClass(),
child: SizedBox(
width: 320,
height: 240,
child: Container(
color: Colors.red,
),
),
),
),
ClipPathClass File
class ClipPathClass extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.moveTo(0.0, size.height - (size.height * 6 / 8));
var secondControlPoint = Offset(size.width / 2, 0);
var secondPoint = Offset(size.width, size.height - (size.height * 6 / 8));
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
secondPoint.dx, secondPoint.dy);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

Flutter custom shape using CustomPaint

I just want my created shape so that i can stack a widget to achieve the Image below. i am trying to get the transparent shape at the back ground of the X and Love. I Try using the shape maker but my mouse designing is not perfect. here is the code generated from the shape maker
child: CustomPaint(
size: Size(400,(400*0.2857142857142857).toDouble()),
painter: RPSCustomPainter(),
),
class RPSCustomPainter extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
Paint paint_0 = new Paint()
..color = Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.stroke
..strokeWidth = 1;
Path path_0 = Path();
path_0.moveTo(size.width*0.2137714,size.height*0.2524000);
path_0.cubicTo(size.width*0.1736143,size.height*0.4775500,size.width*0.1973000,size.height*0.6711500,size.width*0.2153286,size.height*0.7510000);
path_0.cubicTo(size.width*0.2270429,size.height*0.7777500,size.width*0.2705286,size.height*0.9439500,size.width*0.3556000,size.height*0.7521500);
path_0.cubicTo(size.width*0.3856000,size.height*0.6504000,size.width*0.3970143,size.height*0.6162000,size.width*0.4283571,size.height*0.7526000);
path_0.cubicTo(size.width*0.4669286,size.height*0.8264000,size.width*0.5172429,size.height*0.9022500,size.width*0.5719714,size.height*0.7500000);
path_0.cubicTo(size.width*0.6146429,size.height*0.5440500,size.width*0.5914429,size.height*0.3101000,size.width*0.5713714,size.height*0.2514000);
path_0.cubicTo(size.width*0.5520714,size.height*0.1778000,size.width*0.4875429,size.height*0.0767500,size.width*0.4296571,size.height*0.2527000);
path_0.cubicTo(size.width*0.4023714,size.height*0.3646000,size.width*0.3816857,size.height*0.3850000,size.width*0.3557143,size.height*0.2523000);
path_0.cubicTo(size.width*0.3438571,size.height*0.2086000,size.width*0.2652143,size.height*0.0579000,size.width*0.2137714,size.height*0.2524000);
path_0.close();
canvas.drawPath(path_0, paint_0);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
what i am trying to achieve
my result. the shape is not perfect.
thanks
At first I wanted to describe you the ways you can achieve the shape you want and so on...
But got carried away with this fun programming challenge and ended up creating an actual widget :)
It depends on the font_awesome_flutter package, so don't forget to install it (for the heart icon). font_awesome_flutter
So the widget's source code is:
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
extension ToRadians on int {
double get toRadians => this * (math.pi / 180.0);
}
enum _ButtonType { like, dislike }
class LikeOrNot extends StatelessWidget {
final VoidCallback onLike;
final VoidCallback onDislike;
// Percents from total widget width, default - 2%
final _gapSizeRatio = 0.02;
final _likeIconColor = const Color(0xffb85076);
final _dislikeIconColor = Colors.white;
const LikeOrNot({
Key? key,
required this.onLike,
required this.onDislike,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 2,
child: LayoutBuilder(
builder: (context, constraints) {
final buttonPaddings = constraints.maxHeight * 0.1;
final halfWidth = constraints.maxWidth / 2;
return Stack(
children: [
Positioned.fill(
child: CustomPaint(
painter: RPSCustomPainter(
gapSizeRatio: _gapSizeRatio,
),
),
),
Positioned(
left: 0,
bottom: 0,
top: 0,
right: halfWidth + constraints.maxWidth * _gapSizeRatio,
child: SizedBox.expand(
child: Padding(
padding: EdgeInsets.all(buttonPaddings),
child: _buildButton(_ButtonType.dislike),
),
),
),
Positioned(
right: 0,
bottom: 0,
top: 0,
left: halfWidth + constraints.maxWidth * _gapSizeRatio,
child: SizedBox.expand(
child: Padding(
padding: EdgeInsets.all(buttonPaddings),
child: _buildButton(_ButtonType.like),
),
),
),
],
);
},
),
);
}
Widget _buildButton(_ButtonType buttonType) {
final isPositiveAction = buttonType == _ButtonType.like;
return ElevatedButton(
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
primary: isPositiveAction ? _dislikeIconColor : _likeIconColor,
onPrimary: isPositiveAction ? _likeIconColor : _dislikeIconColor,
padding: EdgeInsets.zero,
elevation: 10,
shadowColor: Colors.black54,
),
onPressed: onDislike,
child: FractionallySizedBox(
widthFactor: 0.35,
heightFactor: 0.35,
child: FittedBox(
child: isPositiveAction
? const FaIcon(FontAwesomeIcons.heart)
: const Icon(Icons.close),
),
),
);
}
}
class RPSCustomPainter extends CustomPainter {
final double gapSizeRatio;
RPSCustomPainter({
required this.gapSizeRatio,
});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black.withOpacity(0.08)
..style = PaintingStyle.fill
..strokeWidth = 1;
final path = Path();
final gapSize = size.width * gapSizeRatio;
final arcRadius = size.height / 2 - gapSize / 2;
final leftCircleCenter = Offset(
size.width * 0.25 - gapSize / 2,
size.height / 2,
);
final rightCircleCenter = Offset(
size.width * 0.75 + gapSize / 2,
size.height / 2,
);
path.arcTo(
Rect.fromCircle(
center: leftCircleCenter,
radius: arcRadius,
),
45.toRadians,
270.toRadians,
false,
);
final bezierOffset = arcRadius * (105 / 360);
path.quadraticBezierTo(
size.width / 2,
size.height * 0.30,
rightCircleCenter.dx - arcRadius + bezierOffset,
rightCircleCenter.dy - arcRadius + bezierOffset,
);
path.arcTo(
Rect.fromCircle(
center: rightCircleCenter,
radius: arcRadius,
),
225.toRadians,
270.toRadians,
false,
);
path.quadraticBezierTo(
size.width / 2,
size.height * 0.70,
leftCircleCenter.dx + arcRadius - bezierOffset,
leftCircleCenter.dy + arcRadius - bezierOffset,
);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
It's dynamic, the buttons are built-in. You get 2 options to work with - onDislike() and onLike() callbacks.
An example using different sizes.
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xffc0496f), Color(0xffdb6b59)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
title: const Text('Test'),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Container(
width: double.infinity,
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
for (final size in List.generate(5, (index) => index++))
FractionallySizedBox(
widthFactor: 1.0 - size * 0.2,
child: LikeOrNot(
onLike: () {},
onDislike: () {},
),
),
],
),
),
),
);
}
}
There's a _gapSize parameter which is the gap between two circles. The one you need is already inside (2% default) but you can get some cool other variations by changing it. For example, here's a gap of 20% total width:

Flutter Custom Rectangle

How do I draw something like below using the CustomPainter class in Flutter?
Ignore the "Batch" label in the middle. I just want to achieve that border/box shape with spaces in between the border line.
Try this
You can check it on Github
Or try it on DartPad
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ScanPainter(
size: 200,
color: Colors.grey,
),
),
),
);
}
}
class ScanPainter extends StatelessWidget {
final double size;
final Color color;
ScanPainter({
#required this.size,
this.color = Colors.grey,
}) : assert(size != null);
#override
Widget build(BuildContext context) {
return SizedBox(
height: size,
width: size,
child: Stack(
children: [
Positioned(
top: 0.0,
left: 0.0,
child: _buildRotatedPart(
0,
size * .2,
),
),
Positioned(
top: 0.0,
right: 0.0,
child: _buildRotatedPart(
1.5708,
size * .2,
),
),
Positioned(
bottom: 0.0,
right: 0.0,
child: _buildRotatedPart(
3.14159,
size * .2,
),
),
Positioned(
bottom: 0.0,
left: 0.0,
child: _buildRotatedPart(
4.71239,
size * .2,
),
),
],
),
);
}
Widget _buildRotatedPart(double radians, double size) {
return Transform.rotate(
angle: radians,
child: CustomPaint(
painter: ScanCustomPainter(color: this.color),
size: Size(size, size),
),
);
}
}
class ScanCustomPainter extends CustomPainter {
final Color color;
ScanCustomPainter({
#required this.color,
}) : assert(color != null);
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = this.color;
var path = Path();
double baseHeight = size.height;
double baseWidth = size.width;
path.moveTo(0, baseHeight);
path.lineTo(0, baseHeight * .5);
path.quadraticBezierTo(0, 0, baseWidth * .5, 0);
path.lineTo(baseWidth, 0);
path.lineTo(baseWidth, baseHeight * .3);
path.lineTo(baseWidth * .6, baseHeight * .3);
path.quadraticBezierTo(
baseWidth * .3, baseHeight * .3, baseWidth * .3, baseHeight * .6);
path.lineTo(baseWidth * .3, baseHeight);
path.close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(ScanCustomPainter oldDelegate) => false;
}