
The easiest way to position a widget at a specific location on the screen is to use a Stack and a Positioned widget.
Though, the problem we face is that the positioning (top, right, bottom, left) refers to the side of the child widget and not the center.
So, we need to adjust the positioning.
Solution 1 - Using SizedBox > Center
Positioned(
left: point.dx * width - 24,
top: point.dy * height - 24,
child: SizedBox(
width: 48,
height: 48,
child: Center(
child: Checkbox(
value: true,
onChanged: (_) {},
),
),
),
),
48 has been chosen to be sure we are bigger than the full size (including the padding caused by the materialTapTargetSize and the visualDensity.
This leads us to a second solution.
Solution 2: Computing the full size of the Checkbox
Though the Checkbox has a static const width of 18, it may vary depending on the materialTapTargetSize and the visualDensity.
If we have a look at the source code of the CheckBox on GitHub:

We can define a ComputeCheckBoxSize:
double computeCheckBoxSize(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize =
themeData.checkboxTheme.materialTapTargetSize ??
themeData.materialTapTargetSize;
final VisualDensity effectiveVisualDensity =
themeData.checkboxTheme.visualDensity ?? themeData.visualDensity;
Size size;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(
kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break;
}
size += effectiveVisualDensity.baseSizeAdjustment;
return size.longestSide;
}
Our Positioned widget can be further simplified to:
Positioned(
left: point.dx * width - checkBoxSize / 2,
top: point.dy * height - checkBoxSize / 2,
child: Checkbox(
value: true,
onChanged: (_) {},
),
)
Full source code
import 'dart:math' show Random;
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
double checkBoxSize = computeCheckBoxSize(context);
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
final height = constraints.biggest.height;
final width = constraints.biggest.width;
return Stack(
children: [
Container(color: Colors.amber.shade100),
Positioned.fill(child: CustomPaint(painter: TestPathPainter())),
...points
.map(
(point) => Positioned(
left: point.dx * width - checkBoxSize / 2,
top: point.dy * height - checkBoxSize / 2,
child: Checkbox(
value: true,
onChanged: (_) {},
),
),
)
.toList(),
],
);
},
),
);
}
}
double computeCheckBoxSize(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize =
themeData.checkboxTheme.materialTapTargetSize ??
themeData.materialTapTargetSize;
final VisualDensity effectiveVisualDensity =
themeData.checkboxTheme.visualDensity ?? themeData.visualDensity;
Size size;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(
kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break;
}
size += effectiveVisualDensity.baseSizeAdjustment;
print(size);
return size.longestSide;
}
class TestPathPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..color = Colors.black;
final path = Path()
..moveTo(
points[0].dx * size.width,
points[0].dy * size.height,
);
points.sublist(1).forEach(
(point) => path.lineTo(
point.dx * size.width,
point.dy * size.height,
),
);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(TestPathPainter oldDelegate) => false;
}
final random = Random();
final List<Offset> points = List.generate(
10,
(index) => Offset(.1 + random.nextDouble() * .8, .1 + index * .8 / 9),
);