Use Gradient with Paint object in Flutter Canvas - flutter

I am able to draw a semi circle using the following example:
Flutter how to draw semicircle (half circle)
However, the Paint object only seems to accept a Color (using Paint.color). I would like to add a RadialGradient or LinearGradient as the Color. Is that possible?

Yes! This is totally possible using Paint.shader.
You can either create a gradient shader directly using dart:ui or convert a Flutter gradient to a shader using Gradient.createShader.
dart:ui gradient
import 'dart:ui' as ui;
// In your paint method
final paint = Paint()
..shader = ui.Gradient.linear(
startOffset,
endOffset,
[
color1,
color2,
],
);
A real world example can be seen here.
Flutter painting gradient
import 'package:flutter/painting.dart';
// In your paint method
final paint = Paint()
..shader = RadialGradient(
colors: [
color1,
color2,
],
).createShader(Rect.fromCircle(
center: offset,
radius: radius,
));
An example of this can be found here.
These two are effectively the same. The Flutter painting version simply converts it to a dart:ui gradient (shader) when you call createShader. The reason it exists is that the painting version is better suited for prebuilt widgets like Container.

Related

Flutter : Good way of adding borders to CustomPaint

I am trying to create a more complex customPaint and want to add a border to it.
From the picture you can see the shape (In blue) and the partial border (in white)
The way I am creating the white border is with a secondary customPaint right now.
My question is, is there an easier way of adding a border to the existing customPaint, as doing the corners of the rectangle can be annoying by itself and doesn't seem like an elegant way of doing it.
My code right now, without the border
Path.combine(
PathOperation.intersect,
Path()
..addRRect(RRect.fromLTRBR(
xCanvasLeft,
xCanvasTop,
xCanvasRight,
xCanvasBot,
const Radius.circular(canvasRadius))),
Path()
..addOval(Rect.fromCircle(
center: Offset(xHole, yHoleTop),
radius: holeRadius))
..addOval(Rect.fromCircle(
center: Offset(xHole,
yHoleBot),
radius: holeRadius))
..close(),
),
paint,
);
EDIT: I am also wondering how resource heavy custom paint is?
I simply use the same painter for adding the border with the stroke width property.
Paint paintBorder = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 6.0
..color = Colors.blue;
Paint paintFill = Paint();
Then while drawing the path just draw the fill before the border like this
canvas.drawPath(path, paintFill);
canvas.drawPath(path, paintBorder);

How to recolour a Sprite

I am trying to figure out how to recolour or 'tint' a Sprite using the Flame engine for Flutter. I've tried to add or override a Paint object as described here: How to Add transparency to image.
Using the code below I tried changing the Paint object using a different color than white. The alpha does change, but my Sprite image stays white. My source image is a white png.
void render(Canvas c) {
Sprite spriteImg = Sprite('someImg.png');
rect = Rect.fromLTWH(10, 10, 20, 20);
Paint redPaint = Paint()..color = Colors.red.withOpacity(0.5);
spriteImg.renderRect(c, rect, overridePaint: redPaint);
}
The Flame engine must have a way to tint Sprites that I'm not seeing or using wrong right?
You can use a ColorEffect for that:
yourSpriteComponent
..add(
ColorEffect(
Colors.red,
const Offset(
0.0,
0.5,
), // Means, applies from 0% to 50% of the color
EffectController(
duration: 1.5,
),
),
);

Flutter applying shadows to CustomPainted widget

Introduction
I draw a custom Tab-bar using Flutter's CustomPaint Widget it looks like this:
In Addition, this is the code I used to draw the widget :
class TabBarPainter extends CustomPainter {
final Color paintColor = Colors.redAccent
#override
void paint(Canvas canvas, Size size) {
/// the thing that I called as a deleter circle is an imaginary circle that I used to delete a semicircle from my rectangular shape
var diameter = 80; /// X value is the diameter(R) of the deleter circle;
double topSpace = 2;
double startCurve = (size.width-diameter)/2; /// Start point of the curve
double endCurve = startCurve+diameter; // End Point of the curve
/// Some math about my cubic bezier curve
double xValueInset = diameter * 0.05;
double yValueOffset = (diameter/2) * 4.0 / 3.0;
Path path = Path();
Paint paint = Paint();
/// Points to make a semicircle approximation using Bezier Curve
var firstendPoint = new Offset(endCurve, topSpace);
var controlPoint1 = new Offset(startCurve+xValueInset,yValueOffset+topSpace);
var controlPoint2 = new Offset((diameter-xValueInset)+startCurve,yValueOffset+topSpace);
//! Start sketching Shape
path.moveTo(0.0,topSpace);
path.lineTo(startCurve, topSpace);
path.cubicTo(controlPoint1.dx, controlPoint1.dy,
controlPoint2.dx, controlPoint2.dy,
firstendPoint.dx, firstendPoint.dy);
path.lineTo(size.width, topSpace);
path.lineTo(size.width, size.height);
path.lineTo(0.0, size.height);
path.close();
//! End sketching Shape
paint.color = paintColor;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(TabBarPainter oldDelegate) => oldDelegate != this;
}
Experimenting
I want to apply a shodow on my CustomDrawen Shape so I tried 2 methods:
First Method: I used a classical container and boxshadow to shadow it and it looked like this:
I used the following code :
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color : Colors.black.withOpacity(0.30),
blurRadius: 3,
ffset: new Offset(0, -3)
)
]
),
child: CustomPaint(
painter: TabBarPainter(),
),
Second Method : I drew a similar black colored(opacity(0.15)) shape
and it looked like this:
I added the following code to my CustomPainter in order to draw this shape:
final Color shadowColor = Colors.black.withOpacity(0.15);
Path shadowPath = Path();
Paint shadowPaint = Paint();
/// Points to make a semicircle approximation using Bezier Curve
var shadowfirstendPoint = new Offset(endCurve-topSpace, 0.0);
var shadowcontrolPoint1 = new Offset(startCurve+xValueInset+topSpace,yValueOffset);
var shadowcontrolPoint2 = new Offset((diameter-xValueInset)+startCurve-topSpace,yValueOffset);
//! Start sketching Shadow
shadowPath.lineTo(startCurve+topSpace, 0.0);
shadowPath.cubicTo(shadowcontrolPoint1.dx, shadowcontrolPoint1.dy,
shadowcontrolPoint2.dx, shadowcontrolPoint2.dy,
shadowfirstendPoint.dx, shadowfirstendPoint.dy);
shadowPath.lineTo(size.width, 0.0);
shadowPath.lineTo(size.width, size.height);
shadowPath.lineTo(0.0, size.height);
shadowPath.close();
//! End Sketching Shadow
shadowPaint.color = shadowColor;
canvas.drawPath(shadowPath, shadowPaint);
Problem
In the first method, blank area - at the top center of widget - was filled with shadow
In the second method, shadow was not realistic because even though it had low opacity, it did not had a blur and I could not find a method to make it blurred
Conclusion
I need another way to properly shadow my widget or add blur to my shadow-like custom drawn shape
Since flutter is doing this to every type of widget they created, it seems possible
I would be thankful to any kind of helper
Regards
An easier way I found was to offset the path
canvas.drawShadow(path.shift(Offset(0, -5)), Colors.black, 2.0, true);
As #pskink mentioned, there is a method of canvas called drawShadow
so changing my drawing method for my shadowPath to :
canvas.drawShadow(shadowPath, Colors.black, 2.0, true);
Will solve the problem and the output will be something like this :
Thanks for everyone !!

Can I draw a custom box shadow in Flutter using Canvas in a CustomPaint?

It's clear how to draw a shadow with an elevation property but what if I want to have the shadow centered for example?
Found a solution:
I can simply go into the source code for the BoxShadow widget and apply the path properties they used to my own paths.
Here's the source code.
Here's the code that I used to create a shadow for a custom path (rather than a circle or rectangle with a border radius) that allowed me to create a custom shadow rather than using the elevation preset.
canvas.drawPath(
Path()
..addRect(
Rect.fromPoints(Offset(-15, -15), Offset(size.width+15, size.height+15)))
..addOval(
Rect.fromPoints(Offset(0, 0), Offset(size.width, size.height)))
..fillType = PathFillType.evenOdd,
Paint()
..color= Colors.black.withAlpha(shadowAlpha)
..maskFilter = MaskFilter.blur(BlurStyle.normal, convertRadiusToSigma(3))
);
static double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;
}
To draw shadow on CustomPaint you can use painter.
CustomPaint(
painter: BoxShadowPainter(),
child: ClipPath(
clipper: MyClipper(), //my CustomClipper
child: Container(), // my widgets inside
)));
check my answer here

Why am I getting such bad render glitches using custom paint on Android?

I'm trying to implements Rings that are similar to the Apple Watch rings. I'm drawing these rings as arcs using Canvas and a CustomPaint widget.
canvas.drawArc(
Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2), radius: radius - 10),
-pi / 2,
completion,
false,
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 20
..color = color
..strokeCap = StrokeCap.round,
The rings look like this:
I have a stateful widget that I'm using to increment the rings. This works perfectly fine.
However, I also have a background to the rings that is also drawn using Canvas which consists of arcs, circles, shadows and custom paths. I'm
using a Stack widget to put one on top of the other:
Stack(children: <Widget>[
RingsBackground(),
CustomPaint(
size: Size(300, 300),
painter: CirclePainter(streak: streak),
),
])
When I do this and increment and complete some of the circles, I'm getting really bad rendering glitches but only on Android and not on iOS. Interestingly if I close my app on android and reopen it (or switch to another app and switch back) it works fine. This is what the glitch looks like.
Does anyone know how to fix this or create a workaround? Have I made a mistake?
So I found out that this only happens while using canvas to paint outside the width and heigh that you provide in the CustomPaint widget. Once I restructured everything to fit within those bounds, everything worked fine.