I need to code these white intertwined circles (not the background):
I know how to draw one circle.
What eludes me is the math.
Note:
Yes, trigonometry is high school stuff, I'm perfectly aware.
As bereal said:
The coordinates of the k-th center will be (rcos kx, rsin kx) where r is the radius, and x = 2*pi/n where n is the number of circles you need.
Here is the example how to do it:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: CustomPaint(
foregroundPainter: CustomCirclesPainter(),
),
),
),
);
}
}
class CustomCirclesPainter extends CustomPainter {
var myPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 5.0;
double radius = 80;
#override
void paint(Canvas canvas, Size size) {
int n = 10;
var range = List<int>.generate(n, (i) => i + 1);
for (int i in range) {
double x = 2 * pi / n;
double dx = radius * cos(i * x);
double dy = radius * sin(i * x);
canvas.drawCircle(Offset(dx, dy), radius, myPaint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Related
I'm trying to create the quarter ring shape in the top left corner without success, any help?
I tried creating a container with border radius and then give it a border for the color, but it didn't go as I expected
You can create custom ring circle by this way
Firstly you need to Create custom class same as below
import 'dart:math' as math;
class MyCustomPainter extends CustomPainter {
final learned;
final notLearned;
final range;
final totalQuestions;
double pi = math.pi;
MyCustomPainter({this.learned, this.totalQuestions, this.notLearned, this.range});
#override
void paint(Canvas canvas, Size size) {
double strokeWidth = 7;
Rect myRect = const Offset(-50.0, -50.0) & const Size(100.0, 100.0);
var paint1 = Paint()
..color = Colors.red
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
double firstLineRadianStart = 0;
double _unAnswered = (totalQuestions - notLearned - learned) * range / totalQuestions;
double firstLineRadianEnd = (360 * _unAnswered) * math.pi / 180;
canvas.drawArc(
myRect, firstLineRadianStart, firstLineRadianEnd, false, paint1);
double _learned = (learned) * range / totalQuestions;
double secondLineRadianEnd = getRadians(_learned);
canvas.drawArc(myRect, firstLineRadianEnd, secondLineRadianEnd, false, paint1);
}
double getRadians(double value) {
return (360 * value) * pi / 180;
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Then call that class in following way
Stack(
children: [
//Your body widget will be here
CustomPaint(
painter: MyCustomPainter(
totalQuestions: 300,
learned: 75,
notLearned: 75,
range: 10),
)
],
),
It will look like this
I am successful in drawing a chaos game fractal in flutter with custom paint, it needs 200000 iterations to render a complete fractal. The custom paint widget only shows up after it completes all the iterations. But I want to subsequently draw to the screen, not all at once.
Supporting Code
class Pair<T> {
T x;
T y;
Pair(this.x, this.y);
#override
String toString() {
return "{$x,$y}";
}
}
Color bgColor = Color(0xFF2C394B);
Color screenBgColor = Color(0xFF082032);
Color btnColor = Color(0xFFFF4C29);
Color btnTextColor = Color(0xFF2C394B);
Main Code
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:fractals/constants.dart';
import 'package:fractals/home_page_viewmodel.dart';
import 'package:fractals/utils.dart';
import 'package:provider/provider.dart';
class ChaosGameFractalPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var random = Random();
var center = size / 2;
randomHeight() => (size * random.nextDouble()).height;
randomWidth() => (size * random.nextDouble()).width;
List<Pair> points = [];
var n = 5;
for (int i = 0; i < n; i++) {
var angle = i * (360 / n);
var radians = angle * (pi / 180);
var loc = Pair((cos(radians)), sin(radians));
loc.x *= 200;
loc.y *= 200;
loc.x += center.width;
loc.y += center.height;
print(loc);
points.add(loc);
}
var current = Pair(
lerpDouble(points[0].x, points[1].x, random.nextDouble())!,
lerpDouble(points[0].y, points[1].y, random.nextDouble())!);
var paint = Paint()..color = btnColor;
canvas.drawCircle(Offset(current.x, current.y), 1, paint);
for (int i = 0; i < n; i++) {
canvas.drawCircle(Offset(points[i].x, points[i].y), 1, paint);
}
void render(List<Pair<dynamic>> points, Random random, Pair<double> current,
Canvas canvas, Paint paint) {
var previous;
for (int i = 0; i < 200000; i++) {
var next = points[random.nextInt(points.length)];
if (next != previous) {
current.x = lerpDouble(current.x, next.x, .5)!;
current.y = lerpDouble(current.y, next.y, .5)!;
canvas.drawCircle(Offset(current.x, current.y), .5, paint);
}
previous = next;
}
}
render(points, random, current, canvas, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class ChaosGameFractal extends StatelessWidget {
const ChaosGameFractal({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Consumer<HomePageViewModel>(
builder: (context, value, child) => CustomPaint(
size: Size(400, 500), foregroundPainter: ChaosGameFractalPainter()),
),
);
}
}
The animation begins like this, with both circles in the center, but ends up where one circle reaches the end faster for some reason. How could I make it so that they reach the ends at the same time? I don't understand why this code isn't working. Any help would be much appreciated, thank you!
#override
void paint(Canvas canvas, Size size) {
var percentFinished = _animation.value;
var middleY = size.height / 2;
var middleX = size.width / 2;
double radius = 40;
var angles = [180, 0];
for (var angle in angles) {
var radians = (angle * pi) / 180;
var endingX = middleX + cos(radians) * radius;
var endingY = middleY - sin(radians) * radius;
var addToY = negativeOrPositiveOrZero(sin(radians)) * percentFinished;
var addToX = negativeOrPositiveOrZero(cos(radians)) * percentFinished;
canvas.drawCircle(Offset(endingX * addToX + middleX, endingY * addToY + middleY), 10, Paint());
}
}
int negativeOrPositiveOrZero(double a) {
int num = a.toInt();
if (num > 0){
print("1" + ": " + num.toString());
return 1;
}
else if (num < 0) {
return -1;
}
else {
return 0;
}
}
Below is just some screenshots of what I'm talking about.
The animation starts like this, with two balls in the center
But it ends in this state where one circle reaches the end before the other. The desired behavior is to have them reach their side of the screen at the same time.
I think your problem is how you compute your endingX.
var endingX = middleX + cos(radians) * radius;
It seems that your endingX should be the distance between the side of the Canvas and the perimeter of the Circles in their initial position. It's, therefore, the same for both directions:
var endingX = middleX - radius;
Then, a few simplifications on your code:
negativeOrPositiveOrZero
You have a getter for that in dart:math: sign
Trigonometry
I suppose the sample you posted is much simpler than your real code and the simplifications hereafter are probably not meaningful.
However, pay attention that near zero calculus on computers is quite messy!
import 'dart:math';
void main() {
print(' sin(0).sign: ${sin(0).sign}');
print(' sin(0).sign: ${sin(0).sign}');
print('------------------------------------------');
print(' sin(180*pi/180): ${sin(180*pi/180)}');
print('!! sin(180*pi/180).sign: ${sin(180*pi/180).sign}');
}
sin(0).sign: 0
sin(0).sign: 0
------------------------------------------
sin(180*pi/180): 1.2246467991473532e-16
!! sin(180*pi/180).sign: 1
Full example after correction and simplification
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: 'Rolling balls',
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 200,
color: Colors.amber.shade300,
child: const MyCustomPaint(),
),
),
),
);
}
}
class MyCustomPaint extends StatefulWidget {
const MyCustomPaint({Key? key}) : super(key: key);
#override
_MyCustomPaintState createState() => _MyCustomPaintState();
}
class _MyCustomPaintState extends State<MyCustomPaint>
with SingleTickerProviderStateMixin {
late Animation<double> _animation;
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 4),
);
Tween<double> _animationTween = Tween(begin: 0.0, end: 1.0);
_animation = _animationTween.animate(_controller)
..addListener(() => setState(() {}))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: MyCustomPainter(_animation.value),
);
}
}
class MyCustomPainter extends CustomPainter {
final double percentFinished;
MyCustomPainter(this.percentFinished);
#override
void paint(Canvas canvas, Size size) {
double middleY = size.height / 2;
double middleX = size.width / 2;
double radius = size.width / 20;
Paint paint = Paint()..color = Colors.black;
for (int direction in [1, -1]) {
var endingX = middleX - radius;
var addToX = direction * percentFinished;
canvas.drawCircle(
Offset(endingX * addToX + middleX, middleY),
radius,
paint,
);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
I have used Flutter's CustomPainter class to create a generative still image using Paths (see code below). I'd like to be able to animate such images indefinitely. What is the most straightforward approach to doing this?
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: PathExample(),
),
);
class PathExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: PathPainter(),
);
}
}
class PathPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey[200]
..style = PaintingStyle.fill
..strokeWidth = 0.0;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
Path path2 = Path();
for (double i = 0; i < 200; i++) {
Random r = new Random();
path2.moveTo(sin(i / 2.14) * 45 + 200, i * 12);
path2.lineTo(sin(i / 2.14) * 50 + 100, i * 10);
paint.style = PaintingStyle.stroke;
paint.color = Colors.red;
canvas.drawPath(path2, paint);
}
Path path = Path();
paint.color = Colors.blue;
paint.style = PaintingStyle.stroke;
for (double i = 0; i < 30; i++) {
path.moveTo(100, 50);
// xC, yC, xC, yC, xEnd, yEnd
path.cubicTo(
-220, 300, 500, 600 - i * 20, size.width / 2 + 50, size.height - 50);
canvas.drawPath(path, paint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
To do this, you're going to want to do a lot of what the TickerProviderStateMixin does - essentially, you need to create and manage your own Ticker.
I've done this in a simple builder widget below. It simply schedules a build every time there has been a tick, and then builds with the given value during that ticket. I've added a totalElapsed parameter as well as a sinceLastDraw parameter for convenience but you could easily choose one or the other depending on what's the most convenient for what you're doing.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(
MaterialApp(
home: PathExample(),
),
);
class PathExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TickerBuilder(builder: (context, sinceLast, total) {
return CustomPaint(
painter: PathPainter(total.inMilliseconds / 1000.0),
);
});
}
}
class TickerBuilder extends StatefulWidget {
// this builder function is used to create the widget which does
// whatever it needs to based on the time which has elapsed or the
// time since the last build. The former is useful for position-based
// animations while the latter could be used for velocity-based
// animations (i.e. oldPosition + (time * velocity) = newPosition).
final Widget Function(BuildContext context, Duration sinceLastDraw, Duration totalElapsed) builder;
const TickerBuilder({Key? key, required this.builder}) : super(key: key);
#override
_TickerBuilderState createState() => _TickerBuilderState();
}
class _TickerBuilderState extends State<TickerBuilder> {
// creates a ticker which ensures that the onTick function is called every frame
late final Ticker _ticker = Ticker(onTick);
// the total is the time that has elapsed since the widget was created.
// It is initially set to zero as no time has elasped when it is first created.
Duration total = Duration.zero;
// this last draw time is saved during each draw cycle; this is so that
// a time between draws can be calculated
Duration lastDraw = Duration.zero;
void onTick(Duration elapsed) {
// by calling setState every time this function is called, we're
// triggering this widget to be rebuilt on every frame.
// This is where the indefinite animation part comes in!
setState(() {
total = elapsed;
});
}
#override
void initState() {
super.initState();
_ticker.start();
}
#override
void didChangeDependencies() {
_ticker.muted = !TickerMode.of(context);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final result = widget.builder(context, total - lastDraw , total);
lastDraw = total;
return result;
}
#override
void dispose() {
_ticker.stop();
super.dispose();
}
}
class PathPainter extends CustomPainter {
final double pos;
PathPainter(this.pos);
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey
..style = PaintingStyle.fill
..strokeWidth = 0.0;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
Path path2 = Path();
for (double i = 0; i < 200; i++) {
Random r = new Random();
path2.moveTo(sin(i / 2.14 + pos) * 45 + 200, (i * 12));
path2.lineTo(sin(i / 2.14 + pos) * 50 + 100, (i * 10));
paint.style = PaintingStyle.stroke;
paint.color = Colors.red;
canvas.drawPath(path2, paint);
}
Path path = Path();
paint.color = Colors.blue;
paint.style = PaintingStyle.stroke;
for (double i = 0; i < 30; i++) {
path.moveTo(100, 50);
// xC, yC, xC, yC, xEnd, yEnd
path.cubicTo(
-220,
300,
500,
600 - i * 20,
size.width / 2 + 50,
size.height - 50,
);
canvas.drawPath(path, paint);
}
}
// in this particular case, this is rather redundant as
// the animation is happening every single frame. However,
// in the case where it doesn't need to animate every frame, you
// should implement it such that it only returns true if it actually
// needs to redraw, as that way the flutter engine can optimize
// its drawing and use less processing power & battery.
#override
bool shouldRepaint(PathPainter old) => old.pos != pos;
}
A couple things to note - first off, the drawing is hugely sub-optimal in this case. Rather than re-drawing the background every frame, that could be made into a static background using a Container or DecoratedBox. Secondly, the paint objects are being re-created and used every single frame - if these are constant, they could be instantiated once and re-used over and over again.
Also, since WidgetBuilder is going to be running a lot, you're going to want to make sure that you do as little as possible in its build function - you're not going to want to build up a whole widget tree there but rather move it as low as possible in the tree so it only builds things that are actually animating (as I've done in this case).
To make a widget draggable, it has to be passed to a Draggable like so:
Draggable<String>(
data: "SomeDateToBeDragged",
child: Container(
width: 300,
height: 200,
alignment: Alignment.center,
color: Colors.purple,
child: Image.network(
'https://someServer.com/dog.jpg',
fit: BoxFit.cover,
),
),
...
As far as I know, a Widget is something rectangular. At least, I found nothing else than rectangular widgets.
I'd like to build a sketching app. Thus, I'd like to make a 'two dimensional' connector [a Line between two points] draggable.
How to I make a line draggable?
In other words: I'd like to make a click initiate a drag only if the drag e.g. is on a painted area and not on transparent background of the area. If I would draw a circle, it would drag if the circle would be clicked. If clicked some pixel outside the circle, it should not start a drag.
Flutter is incredible in making things that appear complex really easy.
Here is a solution for a draggable segment of line with two handles for its extremities.
As you see, you can grab either the line itself or its extremities.
A quick overview of the solution:
MyApp is the MaterialApp with the Scaffold
CustomPainterDraggable is the main Widget with the State Management
I used Hooks Riverpod
Have a look how to use freezed
LinePainter a basic Painter to paint the line and the two handles
Utils, a Utility class to calculate the distance between the cursor and the line or handles. The distance defines which part of the drawing should be dragged around.
Part, a Union class defining the different elements of the drawing (line and handles) to better structure the code.
1. Material App
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/all.dart';
part '66070975.sketch_app.freezed.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Draggable Custom Painter',
home: Scaffold(
body: CustomPainterDraggable(),
),
),
);
}
}
2. Part Union
#freezed
abstract class Part with _$Part {
const factory Part.line() = _Line;
const factory Part.a() = _A;
const factory Part.b() = _B;
const factory Part.noPart() = _NoPart;
}
3. CustomPainterDraggable
class CustomPainterDraggable extends HookWidget {
#override
Widget build(BuildContext context) {
final a = useState(Offset(50, 50));
final b = useState(Offset(150, 200));
final dragging = useState(Part.noPart());
return GestureDetector(
onPanStart: (details) => dragging.value =
Utils.shouldGrab(a.value, b.value, details.globalPosition),
onPanEnd: (details) {
dragging.value = Part.noPart();
},
onPanUpdate: (details) {
dragging.value.when(
line: () {
a.value += details.delta;
b.value += details.delta;
},
a: () => a.value += details.delta,
b: () => b.value += details.delta,
noPart: () {},
);
},
child: Container(
color: Colors.white,
child: CustomPaint(
painter: LinePainter(a: a.value, b: b.value),
child: Container(),
),
),
);
}
}
4. LinePainter
class LinePainter extends CustomPainter {
final Offset a;
final Offset b;
final double lineWidth;
final Color lineColor;
final double pointWidth;
final double pointSize;
final Color pointColor;
Paint get linePaint => Paint()
..color = lineColor
..strokeWidth = lineWidth
..style = PaintingStyle.stroke;
Paint get pointPaint => Paint()
..color = pointColor
..strokeWidth = pointWidth
..style = PaintingStyle.stroke;
LinePainter({
this.a,
this.b,
this.lineWidth = 5,
this.lineColor = Colors.black54,
this.pointWidth = 3,
this.pointSize = 12,
this.pointColor = Colors.red,
});
#override
void paint(Canvas canvas, Size size) {
canvas.drawLine(a, b, linePaint);
canvas.drawRect(
Rect.fromCenter(center: a, width: pointSize, height: pointSize),
pointPaint,
);
canvas.drawRect(
Rect.fromCenter(center: b, width: pointSize, height: pointSize),
pointPaint,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
5. Utils
class Utils {
static double maxDistance = 20;
static Part shouldGrab(Offset a, Offset b, Offset target) {
if ((a - target).distance < maxDistance) {
return Part.a();
}
if ((b - target).distance < maxDistance) {
return Part.b();
}
if (shortestDistance(a, b, target) < maxDistance) {
return Part.line();
}
return Part.noPart();
}
static double shortestDistance(Offset a, Offset b, Offset target) {
double px = b.dx - a.dx;
double py = b.dy - a.dy;
double temp = (px * px) + (py * py);
double u = ((target.dx - a.dx) * px + (target.dy - a.dy) * py) / temp;
if (u > 1) {
u = 1;
} else if (u < 0) {
u = 0;
}
double x = a.dx + u * px;
double y = a.dy + u * py;
double dx = x - target.dx;
double dy = y - target.dy;
double dist = math.sqrt(dx * dx + dy * dy);
return dist;
}
}
Full Source Code (Easy to copy paste)
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/all.dart';
part '66070975.sketch_app.freezed.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Draggable Custom Painter',
home: Scaffold(
body: CustomPainterDraggable(),
),
),
);
}
}
#freezed
abstract class Part with _$Part {
const factory Part.line() = _Line;
const factory Part.a() = _A;
const factory Part.b() = _B;
const factory Part.noPart() = _NoPart;
}
class CustomPainterDraggable extends HookWidget {
#override
Widget build(BuildContext context) {
final a = useState(Offset(50, 50));
final b = useState(Offset(150, 200));
final dragging = useState(Part.noPart());
return GestureDetector(
onPanStart: (details) => dragging.value =
Utils.shouldGrab(a.value, b.value, details.globalPosition),
onPanEnd: (details) {
dragging.value = Part.noPart();
},
onPanUpdate: (details) {
dragging.value.when(
line: () {
a.value += details.delta;
b.value += details.delta;
},
a: () => a.value += details.delta,
b: () => b.value += details.delta,
noPart: () {},
);
},
child: Container(
color: Colors.white,
child: CustomPaint(
painter: LinePainter(a: a.value, b: b.value),
child: Container(),
),
),
);
}
}
class LinePainter extends CustomPainter {
final Offset a;
final Offset b;
final double lineWidth;
final Color lineColor;
final double pointWidth;
final double pointSize;
final Color pointColor;
Paint get linePaint => Paint()
..color = lineColor
..strokeWidth = lineWidth
..style = PaintingStyle.stroke;
Paint get pointPaint => Paint()
..color = pointColor
..strokeWidth = pointWidth
..style = PaintingStyle.stroke;
LinePainter({
this.a,
this.b,
this.lineWidth = 5,
this.lineColor = Colors.black54,
this.pointWidth = 3,
this.pointSize = 12,
this.pointColor = Colors.red,
});
#override
void paint(Canvas canvas, Size size) {
canvas.drawLine(a, b, linePaint);
canvas.drawRect(
Rect.fromCenter(center: a, width: pointSize, height: pointSize),
pointPaint,
);
canvas.drawRect(
Rect.fromCenter(center: b, width: pointSize, height: pointSize),
pointPaint,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class Utils {
static double maxDistance = 20;
static Part shouldGrab(Offset a, Offset b, Offset target) {
if ((a - target).distance < maxDistance) {
return Part.a();
}
if ((b - target).distance < maxDistance) {
return Part.b();
}
if (shortestDistance(a, b, target) < maxDistance) {
return Part.line();
}
return Part.noPart();
}
static double shortestDistance(Offset a, Offset b, Offset target) {
double px = b.dx - a.dx;
double py = b.dy - a.dy;
double temp = (px * px) + (py * py);
double u = ((target.dx - a.dx) * px + (target.dy - a.dy) * py) / temp;
if (u > 1) {
u = 1;
} else if (u < 0) {
u = 0;
}
double x = a.dx + u * px;
double y = a.dy + u * py;
double dx = x - target.dx;
double dy = y - target.dy;
double dist = math.sqrt(dx * dx + dy * dy);
return dist;
}
}
As a Container creates a Renderbox, the resulting draggable area will always be a rectangle.
You can create a line using a CustomPainter. However, the line created by this will by itself not be draggable. If you wrap it with a Container, the line will be draggable. But the area that is draggable is then again determined by the size of the container.
I'd suggest you use a Canvas and keep track of the state of your lines by yourself, like in this thread: How to draw custom shape in flutter and drag that shape around?