Last active
June 8, 2020 16:47
-
-
Save Barttje/3a6bfb7b3c8866cb1bfeba2b0466d2fe to your computer and use it in GitHub Desktop.
Detecting clicks on a list of Custompaint Widgets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'dart:math' as math; | |
import 'package:flutter/rendering.dart'; | |
void main() => runApp(HexagonGridDemo()); | |
class HexagonGridDemo extends StatelessWidget { | |
final grid = HexagonGrid(); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: Scaffold( | |
appBar: AppBar( | |
title: Text('Hexagon Grid Demo'), | |
), | |
body: Container( | |
color: Colors.grey[200], | |
padding: EdgeInsets.all(8), | |
child: LayoutBuilder(builder: (context, constraints) { | |
grid.initialize(constraints.maxWidth, constraints.maxHeight); | |
return Listener( | |
onPointerDown: (PointerEvent details) { | |
handleClick(details); | |
}, | |
child: Container( | |
width: constraints.maxWidth, | |
height: constraints.maxHeight, | |
color: Colors.transparent, | |
child: grid, | |
), | |
); | |
}), | |
), | |
), | |
); | |
} | |
handleClick(PointerEvent details) { | |
var hexagon = | |
grid.hexagons.firstWhere((hexagon) => determineClick(hexagon, details)); | |
print(hexagon.model.center); | |
} | |
bool determineClick(HexagonPaint hexagon, PointerEvent details) { | |
final RenderBox hexagonBox = | |
hexagon.model.key.currentContext.findRenderObject(); | |
final result = BoxHitTestResult(); | |
Offset localClick = hexagonBox.globalToLocal(details.position); | |
if (hexagonBox.hitTest(result, position: localClick)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
class HexagonGrid extends StatelessWidget { | |
final GridInitializer gridInitializer = GridInitializer(); | |
final List<HexagonPaint> hexagons = new List<HexagonPaint>(); | |
void initialize(final double screenWidth, final double screenHeight) { | |
if (this.hexagons.isEmpty) { | |
hexagons.addAll(gridInitializer.getHexagons(screenWidth, screenHeight)); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Stack(children: hexagons); | |
} | |
} | |
class GridInitializer { | |
static const int marginY = 5; | |
static const int marginX = 5; | |
static const int nrX = 6; | |
static const int nrY = 9; | |
double radius; | |
double height; | |
double screenWidth; | |
double screenHeight; | |
List<HexagonPaint> getHexagons( | |
final double screenWidth, final double screenHeight) { | |
var hexagons = new List(); | |
this.screenWidth = screenWidth; | |
this.screenHeight = screenHeight; | |
radius = computeRadius(screenWidth, screenHeight); | |
height = computeHeight(computeRadius(screenWidth, screenHeight)); | |
for (int x = 0; x < nrX; x++) { | |
for (int y = 0; y < nrY; y++) { | |
hexagons.add(HexagonPaint(HexagonModel(computeCenter(x, y), radius))); | |
} | |
} | |
return hexagons; | |
} | |
static double computeRadius(double screenWidth, double screenHeight) { | |
var maxWidth = (screenWidth - totalMarginX()) / (((nrX - 1) * 1.5) + 2); | |
var maxHeight = 0.5 * | |
(screenHeight - totalMarginY()) / | |
(heightRatioOfRadius() * (nrY + 0.5)); | |
return math.min(maxWidth, maxHeight); | |
} | |
static double heightRatioOfRadius() => | |
math.cos(math.pi / HexagonPainter.SIDES_OF_HEXAGON); | |
static double totalMarginY() => (nrY - 0.5) * marginY; | |
static int totalMarginX() => (nrX - 1) * marginX; | |
static double computeHeight(double radius) { | |
return heightRatioOfRadius() * radius * 2; | |
} | |
Offset computeCenter(int x, int y) { | |
var centerX = computeX(x); | |
var centerY = computeY(x, y); | |
return Offset(centerX, centerY); | |
} | |
computeY(int x, int y) { | |
var centerY; | |
if (x % 2 == 0) { | |
centerY = y * height + y * marginY + height / 2; | |
} else { | |
centerY = y * height + (y + 0.5) * marginY + height; | |
} | |
double marginsVertical = computeEmptySpaceY() / 2; | |
return centerY + marginsVertical; | |
} | |
double computeEmptySpaceY() { | |
return screenHeight - ((nrY - 1) * height + 1.5 * height + totalMarginY()); | |
} | |
double computeX(int x) { | |
double marginsHorizontal = computeEmptySpaceX() / 2; | |
return x * marginX + x * 1.5 * radius + radius + marginsHorizontal; | |
} | |
double computeEmptySpaceX() { | |
return screenWidth - | |
(totalMarginX() + (nrX - 1) * 1.5 * radius + 2 * radius); | |
} | |
} | |
class HexagonModel { | |
final Offset center; | |
final double radius; | |
final GlobalKey key = GlobalKey(); | |
HexagonModel(this.center, this.radius); | |
} | |
class HexagonPaint extends StatelessWidget { | |
final HexagonModel model; | |
HexagonPaint(this.model); | |
@override | |
Widget build(BuildContext context) { | |
return CustomPaint( | |
key: model.key, | |
painter: HexagonPainter(model.center, model.radius), | |
child: Container(), | |
); | |
} | |
} | |
class HexagonPainter extends CustomPainter { | |
static const int SIDES_OF_HEXAGON = 6; | |
final double radius; | |
final Offset center; | |
HexagonPainter(this.center, this.radius); | |
@override | |
void paint(Canvas canvas, Size size) { | |
Paint paint = Paint()..color = Colors.blue; | |
Path path = createHexagonPath(); | |
canvas.drawPath(path, paint); | |
} | |
Path createHexagonPath() { | |
final path = Path(); | |
var angle = (math.pi * 2) / SIDES_OF_HEXAGON; | |
Offset firstPoint = Offset(radius * math.cos(0.0), radius * math.sin(0.0)); | |
path.moveTo(firstPoint.dx + center.dx, firstPoint.dy + center.dy); | |
for (int i = 1; i <= SIDES_OF_HEXAGON; i++) { | |
double x = radius * math.cos(angle * i) + center.dx; | |
double y = radius * math.sin(angle * i) + center.dy; | |
path.lineTo(x, y); | |
} | |
path.close(); | |
return path; | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) => false; | |
@override | |
bool hitTest(Offset position) { | |
final Path path = createHexagonPath(); | |
return path.contains(position); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment