Back to Posts

Creating Hexagons in Flutter: A Complete Guide

6 min read

Hexagonal shapes are popular in modern UI design, from game boards to profile pictures. In this guide, we'll explore different approaches to create hexagons in Flutter.

Understanding Hexagon Geometry

A regular hexagon is a six-sided polygon with equal sides and angles. The points of a hexagon can be calculated using the following formula:

  • x = center_x + radius * cos(angle)
  • y = center_y + radius * sin(angle)
  • angles = 0°, 60°, 120°, 180°, 240°, 300°

This geometry forms the basis for creating hexagonal shapes in Flutter.

1. Using CustomPaint

The most flexible way to create a hexagon is using CustomPaint:

class HexagonPainter extends CustomPainter {
  final Color color;
  final Paint _paint;

  HexagonPainter({required this.color})
      : _paint = Paint()
          ..color = color
          ..style = PaintingStyle.fill;

  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;

    // Start from the right middle point
    var angle = 0.0;
    path.moveTo(
      center.dx + radius * cos(angle),
      center.dy + radius * sin(angle),
    );

    // Draw lines to each corner
    for (var i = 1; i <= 6; i++) {
      angle = i * pi / 3;
      path.lineTo(
        center.dx + radius * cos(angle),
        center.dy + radius * sin(angle),
      );
    }

    path.close();
    canvas.drawPath(path, _paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

This approach allows you to draw a hexagon with precise control over its appearance.

2. Using ClipPath

For clipping content (like images) into a hexagonal shape, you can use ClipPath with a custom clipper:

class HexagonClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;

    var angle = 0.0;
    path.moveTo(
      center.dx + radius * cos(angle),
      center.dy + radius * sin(angle),
    );

    for (var i = 1; i <= 6; i++) {
      angle = i * pi / 3;
      path.lineTo(
        center.dx + radius * cos(angle),
        center.dy + radius * sin(angle),
      );
    }

    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

This method is ideal for creating hexagonal masks for images or other widgets.

3. Animated Hexagon

You can create animated hexagons using AnimationController and Transform:

class AnimatedHexagon extends StatefulWidget {
  @override
  _AnimatedHexagonState createState() => _AnimatedHexagonState();
}

class _AnimatedHexagonState extends State<AnimatedHexagon>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _rotationAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    )..repeat();

    _rotationAnimation = Tween<double>(
      begin: 0,
      end: 2 * pi,
    ).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _rotationAnimation,
      builder: (context, child) {
        return Transform.rotate(
          angle: _rotationAnimation.value,
          child: CustomPaint(
            painter: HexagonPainter(color: Colors.blue),
            size: Size(100, 100),
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

This approach adds dynamic movement to your hexagonal shapes, making them more engaging.

Best Practices

  1. Size Considerations

    • Keep the hexagon size proportional.
    • Use even numbers for width and height.
    • Consider device pixel ratio.
  2. Performance

    • Cache the paint operations when possible.
    • Use shouldRepaint effectively.
    • Consider using RepaintBoundary.
  3. Accessibility

    • Add semantic labels for screen readers.
    • Ensure sufficient contrast ratio.
    • Provide alternative text for important shapes.

Complete Example

Here's a complete example combining all techniques:

class HexagonWidget extends StatelessWidget {
  final double size;
  final Color color;
  final Widget? child;

  const HexagonWidget({
    Key? key,
    required this.size,
    required this.color,
    this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: size,
      height: size,
      child: ClipPath(
        clipper: HexagonClipper(),
        child: Container(
          color: color,
          child: child,
        ),
      ),
    );
  }
}

// Usage
HexagonWidget(
  size: 100,
  color: Colors.blue,
  child: Center(
    child: Text(
      'Hex',
      style: TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
    ),
  ),
)

By following this guide, you can create beautiful hexagonal shapes in your Flutter applications. Experiment with different colors, gradients, and animations to create unique designs that match your app's style.