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;
}
}
Related
I am trying to create a custom tooltip with the triangle shape on either side. I have created a bubble but how to add the triangle in there without using any library?
class SdToolTip extends StatelessWidget {
final Widget child;
final String message;
const SdToolTip({
required this.message,
required this.child,
});
#override
Widget build(BuildContext context) {
return Center(
child: Tooltip(
child: child,
message: message,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.6),
borderRadius: BorderRadius.circular(22)),
textStyle: const TextStyle(
fontSize: 15, fontStyle: FontStyle.italic, color: Colors.white),
),
);
}
}
You can do it by CustomPainter without any library.
Example 1:
Create Custom Painter Class,
class customStyleArrow extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = Colors.white
..strokeWidth = 1
..style = PaintingStyle.fill;
final double triangleH = 10;
final double triangleW = 25.0;
final double width = size.width;
final double height = size.height;
final Path trianglePath = Path()
..moveTo(width / 2 - triangleW / 2, height)
..lineTo(width / 2, triangleH + height)
..lineTo(width / 2 + triangleW / 2, height)
..lineTo(width / 2 - triangleW / 2, height);
canvas.drawPath(trianglePath, paint);
final BorderRadius borderRadius = BorderRadius.circular(15);
final Rect rect = Rect.fromLTRB(0, 0, width, height);
final RRect outer = borderRadius.toRRect(rect);
canvas.drawRRect(outer, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Wrap your text widget with CustomPaint,
return CustomPaint(
painter: customStyleArrow(),
child: Container(
padding: EdgeInsets.only(left: 15, right: 15, bottom: 20, top: 20),
child: Text("This is the custom painter for arrow down curve",
style: TextStyle(
color: Colors.black,
)),
),
);
Example 2:
Check below example code for tooltip shapedecoration
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,
),
home: const MyHomePage(title: 'Customize Tooltip'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Tooltip(
child: const IconButton(
icon: Icon(Icons.info, size: 30.0),
onPressed: null,
),
message: 'Hover Icon for Tooltip...',
padding: const EdgeInsets.all(20),
showDuration: const Duration(seconds: 10),
decoration: ShapeDecoration(
color: Colors.blue,
shape: ToolTipCustomShape(),
),
textStyle: const TextStyle(color: Colors.white),
preferBelow: false,
verticalOffset: 20,
),
),
);
}
}
class ToolTipCustomShape extends ShapeBorder {
final bool usePadding;
ToolTipCustomShape({this.usePadding = true});
#override
EdgeInsetsGeometry get dimensions =>
EdgeInsets.only(bottom: usePadding ? 20 : 0);
#override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => Path();
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
rect =
Rect.fromPoints(rect.topLeft, rect.bottomRight - const Offset(0, 20));
return Path()
..addRRect(
RRect.fromRectAndRadius(rect, Radius.circular(rect.height / 3)))
..moveTo(rect.bottomCenter.dx - 10, rect.bottomCenter.dy)
..relativeLineTo(10, 20)
..relativeLineTo(10, -20)
..close();
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
Wrap your widget with CustomPaint refer to this article https://medium.com/flutter-community/a-deep-dive-into-custompaint-in-flutter-47ab44e3f216 and documentation for more info, should do the trick.
Try this package https://pub.dev/packages/shape_of_view_null_safe
ShapeOfView(
shape: BubbleShape(
position: BubblePosition.Bottom,
arrowPositionPercent: 0.5,
borderRadius: 20,
arrowHeight: 10,
arrowWidth: 10
),
//Your Data goes here
child: ...,
)
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,
);
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");
},
)),
),
);
}
}
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;
}
I want to create something like this:
How can I do that in flutter? I can't find any similar lib for that.
You can create a custom CircularProgressIndicator using CustomPainter.
class CustomCircularProgress extends CustomPainter {
final double value;
CustomCircularProgress({required this.value});
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawArc(
Rect.fromCenter(center: center, width: 170, height: 170),
vmath.radians(140),
vmath.radians(260),
false,
Paint()
..style = PaintingStyle.stroke
..color = Colors.black12
..strokeCap = StrokeCap.round
..strokeWidth = 20,
);
canvas.saveLayer(
Rect.fromCenter(center: center, width: 200, height: 200),
Paint(),
);
const Gradient gradient = SweepGradient(
startAngle: 1.25 * math.pi / 2,
endAngle: 5.5 * math.pi / 2,
tileMode: TileMode.repeated,
colors: <Color>[
Colors.blueAccent,
Colors.lightBlueAccent,
],
);
canvas.drawArc(
Rect.fromCenter(center: center, width: 170, height: 170),
vmath.radians(140),
vmath.radians(260 * value),
false,
Paint()
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..shader = gradient
.createShader(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..strokeWidth = 20,
);
canvas.restore();
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Then use CustomPaint to render the CustomPainter on the Screen.
CustomPaint(painter: CustomCircularProgress(value: 0.75))
Complete Sample
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as vmath;
import 'dart:math' as math;
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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late AnimationController controller;
#override
void initState() {
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 5),
)..addListener(() {
setState(() {});
});
controller.repeat(reverse: true);
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: CustomPaint(painter: CustomCircularProgress(value: controller.value)),
),
);
}
}
class CustomCircularProgress extends CustomPainter {
final double value;
CustomCircularProgress({required this.value});
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawArc(
Rect.fromCenter(center: center, width: 170, height: 170),
vmath.radians(140),
vmath.radians(260),
false,
Paint()
..style = PaintingStyle.stroke
..color = Colors.black12
..strokeCap = StrokeCap.round
..strokeWidth = 20,
);
canvas.saveLayer(
Rect.fromCenter(center: center, width: 200, height: 200),
Paint(),
);
const Gradient gradient = SweepGradient(
startAngle: 1.25 * math.pi / 2,
endAngle: 5.5 * math.pi / 2,
tileMode: TileMode.repeated,
colors: <Color>[
Colors.blueAccent,
Colors.lightBlueAccent,
],
);
canvas.drawArc(
Rect.fromCenter(center: center, width: 170, height: 170),
vmath.radians(140),
vmath.radians(260 * value),
false,
Paint()
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..shader = gradient
.createShader(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..strokeWidth = 20,
);
canvas.restore();
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}