I want to create a gradient shader.
The Gradient.radial() constructor should return one.
However, VS Code claims that this method is not defined for Gradient. huh.
Now, from what I've seen online, you can create a RadialGradient object and then use .createShader(rect) to convert it into a shader (haven't got it work yet, but maybe I'm implementing it wrong), but that can't be the intended way, right?
Update:
As #pskink pointed out, I'm using a newer version of Flutter and have to import the Gradient shader class as part of the Dart ui library.
The resulting code is valid and looks like this:
paint.shader = ui.Gradient.radial(
Offset(-.5, -.5),
1, // The radius
List.from({
Colors.lightBlueAccent,
Colors.blue,
Colors.deepPurpleAccent},
growable: false
)
);
Unfortunately it doesn't render anything.
This is how the code with the .createShader() method on the other Gradient class looks.
RadialGradient _gradient = RadialGradient(
colors: List.from({
Colors.lightBlueAccent,
Colors.blue,
Colors.deepPurpleAccent},
growable: false),
);
// center is the center of the canvas
paint.shader = _gradient.createShader(
Rect.fromCircle(
center: center,
radius: size.shortestSide / 2
)
);
This is kinda ugly IMO (and less efficient?), but it does render the expected result.
2nd Update:
When having more than 2 color elements in the Gradient shader class you can't omit the stops. This info is just being presented as an errorlog, which is why I didn't see it. With that being fixed: Both versions return different results...
This code returns this gradient:
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final paint = Paint();
RadialGradient _gradient = RadialGradient(
radius: 1,
center: Alignment(-.5, -.5),
tileMode: TileMode.clamp,
colors: List.from({Colors.lightBlueAccent, Colors.blue, Colors.deepPurpleAccent}, growable: false),
stops: List.from({0.0, 0.5, 1.0}, growable: false),
);
paint.shader = _gradient.createShader(
Rect.fromCircle(
center: center,
radius: size.shortestSide / 2
)
);
canvas.drawCircle(center, size.shortestSide / 2, paint);
}
While this code renders this gradient:
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final paint = Paint();
paint.shader = ui.Gradient.radial(
Offset(-.5, -.5),
1, // this is the radius
List.from({Colors.lightBlueAccent, Colors.blue, Colors.deepPurpleAccent},
growable: false),
List.from({0.0, 0.5, 1.0}, growable: false),
TileMode.clamp,
);
canvas.drawCircle(center, size.shortestSide / 2, paint);
}
Related
I'm attempting to expand the click area of a group of shapes, but there appears to be no hitFunc property on groups.
var patternControl = new Konva.Group();
patternControl.hitFunc(function(context) {
context.beginPath();
context.arc(0, 0, outerRadius + patternWidth, 0, Math.PI * 2, true);
context.fillStrokeShape(this);
});
Is there any way to do apply custom hit functions to a group?
Only shapes can be used for hit detection. As a workaround, you can disable hits for all shapes with shape.listeting(false) and then add a "fake" shape to the group that will be used as a hit area:
var patternControl = new Konva.Group();
var hitShape = new Konva.Shape({
// make it transparent, so it is not visible
fill: 'rgba(0,0,0,0)',
hitFunc: (context, shape) => {
context.beginPath();
context.arc(0, 0, outerRadius + patternWidth, 0, Math.PI * 2, true);
context.fillStrokeShape(shape);
}
});
patternControl.add(hitShape);
I'm trying to flip the progress value of an AnimatedIcon. For example:
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller.view,
),
Right now the icon animation is backwards for what I need. So when I'm expecting _controller.view to be 0.0 it's actually showing 1.0.
I tried:
progress: _controller.view == 1.0 ? 0.0 : 1.0
but _controller.view is an Animation<double> and not just a <double>.
How can I set progress to a hard coded value?
You can use a Tween to create an animation that transforms the controller's range of values [0.0, 1.0] to an inverted range [1.0, 0.0] using Tween.animate. For example,
// create your tween.
final tween = Tween<double>(begin: 1.0, end: 0.0);
// apply it to a controller to create an animation.
final animation<double> = tween.animate(controller);
AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: animation,
);
I find using ReverseAnimation is more intuitive for me
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: ReverseAnimation(_controller),
),
Using the latest flutter I would like to draw an arch from 0 to 10 using the Canvas.drawArc method. Here's what my code looks like:
#override
void paint(Canvas canvas, Size size) {
final double radius = 1000.0;
final Paint paint = new Paint()
..isAntiAlias = true
..strokeWidth = 1.0
..color = Colors.blue[500]
..style = PaintingStyle.stroke;
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width/2, size.height/2),
10.0, 20.0, false, paint);
}
It's being drawn inside of a CustomPainter, and is called in the layout like this:
children: <Widget>[
new CustomPaint(
painter: new CircleGraphWidget(),
child: new Center(
child: new Text(
'Here\'s text',
style: const TextStyle(
fontSize: 40.0,
fontWeight: FontWeight.w900,
color: const Color(0xFFFFFFFFF),
),
),
),
)
],
I expect the drawArc call to draw an arc within the rect from 10 to 20 on the circle, however this is what I get:
I'm wondering what it takes to draw just a fraction of the oval instead of the whole thing. For instance, if I only wanted 1/4 of the circle drawn, how would I go about it?
How to draw arcs
To paint in Flutter you use the CustomPaint widget. The CustomPaint widget takes a CustomPainter object as a parameter. In that class you have to override the paint method, which gives you a canvas that you can paint on. Here is the code to draw the arc in the image above.
// Don't forget: import 'dart:math' as math;
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(50, 100, 250, 200);
final startAngle = -math.pi / 2;
final sweepAngle = math.pi;
final useCenter = false;
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 4;
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
}
Notes:
The rect is what the full oval would be inscribed within.
The startAngle is the location on the oval that the line starts drawing from. An angle of 0 is at the right side. Angles are in radians, not degrees. The top is at 3π/2 (or -π/2), the left at π, and the bottom at π/2.
The sweepAngle is how much of the oval is included in the arc. Again, angles are in radians. A value of 2π would draw the entire oval.
If you set useCenter to true, then there will be a straight line from both sides of the arc to the center.
Context
Here is the main.dart code so that you can see it in context.
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: HomeWidget(),
),
);
}
}
class HomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint( // <-- CustomPaint widget
size: Size(300, 300),
painter: MyPainter(),
),
);
}
}
class MyPainter extends CustomPainter { // <-- CustomPainter class
#override
void paint(Canvas canvas, Size size) {
// <-- Insert your painting code here.
}
#override
bool shouldRepaint(CustomPainter old) {
return false;
}
}
See also
See this article for my fuller answer.
https://docs.flutter.io/flutter/dart-ui/Canvas/drawArc.html
It starts from startAngle radians around the oval up to startAngle + sweepAngle radians around the oval, with zero radians
It expects Radians instead of Degrees:
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width/2, size.height/2),
0.175, 0.349, false, paint);
I want to draw a custom shape similar to the marked area of below image. Is there a way to clip this custom shape with blur effect and then specify the radius for the corners?
Here is the
full example
class Customclipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = new Path();
path.lineTo(0.0, size.height - 20);
path.quadraticBezierTo(0.0, size.height, 20.0, size.height);
path.lineTo(size.width - 20.0, size.height);
path.quadraticBezierTo(size.width, size.height, size.width, size.height - 20);
path.lineTo(size.width, 50.0);
path.quadraticBezierTo(size.width, 30.0, size.width - 20.0, 30.0);
path.lineTo(20.0, 5.0);
path.quadraticBezierTo(0.0, 0.0, 0.0, 20.0);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Created all the rounded corners using quadraticBezierTo
Created a Container inside a ClipPath
Used the Colors.white70 for the container color
I use this library, which helps to build shapes from svg files:
https://fluttershapemaker.com/
All you need to do is, export your shape as svg and paste it there
I'm drawing map roads with THREE.Ribbon, every road has border with different color, so I use two ribbons, one is wider with the color of border, one is narrower, then I can achieve my purpose.
I also draw cap on two ends of ribbon with circles, certainly draw two circles, the smaller one covers bigger one, just like linecap in canvas2D.
I can't use THREE.Line as my alternative, because the maximum value of width of Line is 1 in my webgl.
My codes are just like this:
var circleShape = new THREE.Shape();
var circleRadius = lineWidth/2;
circleShape.moveTo( 0, circleRadius );
circleShape.quadraticCurveTo( circleRadius, circleRadius, circleRadius, 0 );
circleShape.quadraticCurveTo( circleRadius, -circleRadius, 0, -circleRadius );
circleShape.quadraticCurveTo( -circleRadius, -circleRadius, -circleRadius, 0 );
circleShape.quadraticCurveTo( -circleRadius, circleRadius, 0, circleRadius );
var circle = new THREE.ShapeGeometry( circleShape);
var material = new THREE.MeshBasicMaterial( { color: color, depthWrite: false} );
var mesh = new THREE.Mesh( circle, material );
this.parent.add( mesh );
var material = new THREE.MeshBasicMaterial( { color: widerColor, depthWrite: false} );
var widerRibbon = new THREE.Ribbon( widerGeometry, material );
this.parent.add( widerRibbon );
var material = new THREE.MeshBasicMaterial( { color: narrowerColor, depthWrite: false} );
var narrowerRibbon = new THREE.Ribbon( narrowerGeometry, material );
this.parent.add( narrowerRibbon );
My logic is the latter will cover the former. so narrower ribbon will cover wider ribbon, ribbon will cover half of circle.
My difficulty:
The consequence is they cover each other(without depthWrite:false).
I have found that THREE.MeshBasicMaterial can set depthWrite to false, I add it, then I find that narrower ribbon covered wider ribbon successfully, but what is weird is the cap on the ends of ribbon seems abnormal, when I look at the ribbon right on top, it works well, but when I look it with a angle, I find that the ribbon can't cover circle.
I don't know how to deal this issue, I just want the later drawing covers the former drawing when they all have same z coordinate.
Thanks for your guidance!
If you want to use "painter's algorith" (whatevr is painted last is always what you see), just turn Z off in the material(s). This is, set the "depthTest" attribute to false when declaring the material