flutter CustomPainter - how to cut out a hole in line path - flutter

I have a CustomPaint which paints an oval.
I want to cut out a hole at a specific position which I couldn't figure out yet how that works.
I tried:
canvas.drawPath(
Path.combine(PathOperation.difference, ovalPath, holePath),
ovalPaint,
);
but that doesn't cut the hole, but gives me the following result:
But this is what I want to achieve:
This oval is just an example, the "real" custom paint is gonna get more complex and I need more than just one cutout. So just painting several lines is not an alternative. I want to first define the path and then apply a cutout (or even inverted clipping) to get the hole.
Is that possible?
Here is a full working example of what I have:
import 'package:flutter/material.dart';
import 'dart:math';
const 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: const Scaffold(
body: Center(
child: OvalCustomPaint(),
),
),
);
}
}
class OvalCustomPaint extends StatelessWidget {
const OvalCustomPaint({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: CustomPaint(
painter: _Painter(),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
),
),
);
},
),
);
}
}
class _Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
const curveRadius = 50.0;
const legLength = 150.0;
canvas.rotate(pi/2);
final ovalPaint = Paint()
..color = Colors.blue
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
const fixPoint = Offset.zero;
//* OVAL LINE
final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
ovalPath.relativeArcToPoint(
const Offset(curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, legLength);
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength);
//* CLP HOLE
final holePath = Path();
holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);
canvas.drawPath(
Path.combine(PathOperation.difference, ovalPath, holePath),
ovalPaint,
);
}
#override
bool shouldRepaint(_Painter oldDelegate) => false;
}

Okay I found a solution for it.
I created a rect path with the size of the CustomPainter area and built the difference with the cutout hole path.
That created a cutout template:
final rectWithCutout = Path.combine(
PathOperation.difference,
Path()
..addRect(
Rect.fromCenter(
center: Offset.zero,
width: size.width,
height: size.height,
),
),
holePath);
Which I could clip the canvas with: canvas.clipPath(rectWithCutout);
The final result:
This is the working full code example again:
import 'dart:math';
import 'package:flutter/material.dart';
const 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: const Scaffold(
body: Center(
child: OvalCustomPaint(),
),
),
);
}
}
class OvalCustomPaint extends StatelessWidget {
const OvalCustomPaint({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: CustomPaint(
painter: _Painter(),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
),
),
);
},
),
);
}
}
class _Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
const curveRadius = 40.0;
const legLength = 130.0;
canvas.rotate(pi / 2);
final ovalPaint = Paint()
..color = Colors.blue
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
const fixPoint = Offset.zero;
//* OVAL LINE
final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
ovalPath.relativeArcToPoint(
const Offset(curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, legLength);
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength);
//* CLIP HOLE
final holePath = Path();
holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);
final rectWithCutout = Path.combine(
PathOperation.difference,
Path()
..addRect(
Rect.fromCenter(
center: Offset.zero,
width: size.width,
height: size.height,
),
),
holePath);
canvas.clipPath(rectWithCutout);
canvas.drawPath(
ovalPath,
ovalPaint,
);
}
#override
bool shouldRepaint(_Painter oldDelegate) => false;
}

You can control the length of the line.
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength + (13 * 2)); //here based on radius
canvas.drawPath(
ovalPath,
ovalPaint,
);

Related

How to make like this appbar?

appBar: AppBar(
title: Center(
child: SvgPicture.asset("assets/images/logo.svg",
height: 15, width: 15, color: Colors.white,),
),
backgroundColor: const Color.fromRGBO(91, 189, 146, 1),
),
How to add the wave at the bottom of the appbar as in the picture?
The following would do the trick. It uses a CustomPaint widget to draw the semi-circle in the middle and Transform.translate to move the "icon" a bit down.
This is the result (also, check the live demo on the DartPad)
The minimal-reproducible-example source code
import 'dart:math';
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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 60,
flexibleSpace: const CustomPaint(
painter: MyCustomPainter(),
size: Size.infinite,
),
title: Center(
child: Transform.translate(
offset: const Offset(0, 5),
child: const Center(
child: Text(
"Q",
style: TextStyle(color: Colors.white, fontSize: 30),
),
),
),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
);
}
}
class MyCustomPainter extends CustomPainter {
const MyCustomPainter({Listenable? repaint}) : super(repaint: repaint);
static const circleSize = 90.0;
static const gap = 15.0;
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.fill
..color = const Color.fromRGBO(91, 189, 146, 1);
var shadow = Paint()
..style = PaintingStyle.fill
..color = const Color.fromARGB(255, 127, 127, 127)
..maskFilter = MaskFilter.blur(
BlurStyle.normal,
Shadow.convertRadiusToSigma(5),
);
var path = Path();
path.lineTo(0, size.height - gap);
path.lineTo(size.width / 2, size.height - gap);
path.arcTo(
Rect.fromLTWH(
size.width / 2 - circleSize / 2,
size.height - circleSize,
circleSize,
circleSize,
),
pi,
-pi,
false,
);
path.lineTo(size.width / 2, size.height - gap);
path.lineTo(size.width, size.height - gap);
path.lineTo(size.width, 0);
path.close();
canvas.drawPath(path, shadow);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(MyCustomPainter oldDelegate) {
return false;
}
}

Dynamic filled star in flutter

Am trying to create a star rating component which can dynamically fill from left.
So far I am able to create a crude star by using custom painter.
class StarPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
double w = size.width;
double h = size.height;
Path path = Path()
..moveTo(0, (2 * h) / 5)
..lineTo(w, (2 * h) / 5)
..lineTo(w / 5, h)
..lineTo(w / 2, 0)
..lineTo((4 * w) / 5, h)
..lineTo(0, (2 * h) / 5)
..close();
Paint fillPaint = Paint()
..style = PaintingStyle.fill
..color = Colors.black;
// canvas.drawPath(path, paint);
canvas.drawPath(path, fillPaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
But I am not able to figure out how to show a portion of it as filled. As of now I have used a double stack one with +2 size which acts as the border and used a shader mask with linear gradient to show progress.
But can this be done without the crude way of stacking ?
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
height: starSize,
width: starSize,
child: Stack(
fit: StackFit.expand,
children: [
DynamicStar(percent: 1.0),
Center(
child: SizedBox(
width: starSize - 2,
height: starSize - 2,
child: DynamicStar(percent: 0.8),
),
)
],
),
),
),
),
);
}
class DynamicStar extends StatelessWidget {
final double percent;
const DynamicStar({Key? key, required this.percent}) : super(key: key);
#override
Widget build(BuildContext context) {
return ShaderMask(
blendMode: BlendMode.srcATop,
shaderCallback: (bounds) {
return LinearGradient(
tileMode: TileMode.clamp,
colors: [Colors.black, Colors.white],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [percent, percent],
).createShader(bounds);
},
child: CustomPaint(painter: StarPainter()),
);
}
}
EDIT: am trying to learn in flutter, so any clues on how to fix my code or snippets would help me better.
Please refer to below link
https://pub.dev/packages/smooth_star_rating
Or
https://pub.dev/packages/flutter_rating_bar
Smooth star rating example
import 'package:flutter/material.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var rating = 1.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Star rating',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SmoothStarRating(
rating: rating,
isReadOnly: false,
size: 80,
filledIconData: Icons.star,
halfFilledIconData: Icons.star_half,
defaultIconData: Icons.star_border,
starCount: 5,
allowHalfRating: true,
spacing: 2.0,
onRated: (value) {
print("rating value -> $value");
},
)),
),
);
}
}

how to create a styling in button in flutter?

How make this styling for a button in flutter. Since I'm a newbie, I have no Idea on how to do it.
In image the triangle is somewhat irregular, I want that to be matched with the button box.
You can use the CustomPaint widget to get that effect. You can easily auto-generate the CustomPaint code from Flutter Shape Maker. Please see the code below. You can also run the code on DartPad at the following URL https://dartpad.dev/0171e4b838b740ea23f896f3b0be1f8e :
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 360,
height: 100,
color: Colors.black,
child: CustomPaint(
size: Size(1200, 700),
painter: FlagPainter(),
child: Text(
"text",
style: TextStyle(color: Colors.black, fontSize:24),
),
),
),
),
);
}
}
class FlagPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint_0 = new Paint()
..color = Color.fromARGB(255, 255, 255, 255)
..style = PaintingStyle.fill
..strokeWidth = 1;
Path path_0 = Path();
path_0.moveTo(0, 0);
path_0.lineTo(0, size.height * 2.40);
path_0.lineTo(0, size.height * 0.72);
path_0.lineTo(size.width * 0.28, 0);
canvas.drawPath(path_0, paint_0);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

How to make this arc in flutter

I want to work exactly like this image with the circles inside it, how can I do it?
Here is your widget
Center(
child: Container(
width: 200,
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20.0)),
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF9B6EEF), Color(0xFF715ED7)]),
),
child: ClipRect(child: CustomPaint(painter: CirclePainter())),
),
),
CustomPainter class for rings inside
class CirclePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.white10
..style = PaintingStyle.stroke
..strokeWidth = 25;
canvas.drawCircle(Offset.zero, 60, paint);
canvas.drawCircle(Offset(size.width, size.height), 60, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Result
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SO(),
debugShowCheckedModeBanner: false,
);
}
}
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ClipRRect(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24)),
child: CustomPaint(
size: Size(250, 200),
painter: SOP(
fillColor: Colors.indigo,
spreadColor: Colors.indigoAccent,
spread: 25,
radius: 100,
),
),
),
),
);
}
}
class SOP extends CustomPainter {
final double spread; //the thickness of inner circles
final Color spreadColor; //the color of inner circles
final Color fillColor; //the background color
final double radius; //the radius of inner circles
final Paint p;
SOP({
#required this.spread,
#required this.spreadColor,
#required this.fillColor,
#required this.radius,
}) : p = Paint()
..strokeWidth = spread
..style = PaintingStyle.stroke
..color = spreadColor;
#override
void paint(Canvas canvas, Size size) {
canvas.drawColor(fillColor, BlendMode.src);
canvas.drawCircle(Offset(0, 0), radius, p);
canvas.drawCircle(Offset(size.width, size.height), radius, p);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Make part of square transparent- Flutter

I want to achieve the scanner view as given in the picture. I've used BoxDecoration to design square with rounded corners.
Center(
child: Container(
width: BarReaderSize.width,
height: BarReaderSize.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white, width: 3)),
),
)
Can someone help ?
You can try to use CustomPaint with some clipping with paths.
Please find the full example here in the DartPad.
The tricky part is to determine how to clip the rounded white rectangle borders. I just used custom Path for that. I created custom Rects and created a Path out of them:
final path = Path()
..addRect(clippingRect0)
..addRect(clippingRect1)
..addRect(clippingRect2)
..addRect(clippingRect3);
It may be not the most efficient approach, but sometimes it's faster to draw something with CustomPainter than to experiment with some already provided widgets. This way you will have always the same look no matter how the API of the material widget changes.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
child: Stack(
children: [
Center(
child: FlutterLogo(
size: 800,
),
),
Container(
child: Center(
child: CustomPaint(
painter: BorderPainter(),
child: Container(
width: BarReaderSize.width,
height: BarReaderSize.height,
),
),
),
),
],
),
),
);
}
}
class BorderPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final width = 3.0;
final radius = 20.0;
final tRadius = 2 * radius;
final rect = Rect.fromLTWH(
width,
width,
size.width - 2 * width,
size.height - 2 * width,
);
final rrect = RRect.fromRectAndRadius(rect, Radius.circular(radius));
final clippingRect0 = Rect.fromLTWH(
0,
0,
tRadius,
tRadius,
);
final clippingRect1 = Rect.fromLTWH(
size.width - tRadius,
0,
tRadius,
tRadius,
);
final clippingRect2 = Rect.fromLTWH(
0,
size.height - tRadius,
tRadius,
tRadius,
);
final clippingRect3 = Rect.fromLTWH(
size.width - tRadius,
size.height - tRadius,
tRadius,
tRadius,
);
final path = Path()
..addRect(clippingRect0)
..addRect(clippingRect1)
..addRect(clippingRect2)
..addRect(clippingRect3);
canvas.clipPath(path);
canvas.drawRRect(
rrect,
Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = width,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class BarReaderSize {
static double width = 200;
static double height = 200;
}