hexagonal 0.0.1 copy "hexagonal: ^0.0.1" to clipboard
hexagonal: ^0.0.1 copied to clipboard

A powerful Flutter package for creating hexagonal UI layouts with grid system, free layout, pattern generators, and boundary-aware generation.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:hexagonal/free_layout/enhanced_hex_widget.dart';
import 'package:hexagonal/free_layout/enhanced_hex_renderer.dart';
import 'package:hexagonal/free_layout/data_driven_hex_generator.dart';

void main() {
  runApp(const EnhancedExampleApp());
}

class EnhancedExampleApp extends StatelessWidget {
  const EnhancedExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Enhanced Hexagon Features',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const EnhancedExamplePage(),
    );
  }
}

class EnhancedExamplePage extends StatefulWidget {
  const EnhancedExamplePage({super.key});

  @override
  State<EnhancedExamplePage> createState() => _EnhancedExamplePageState();
}

class _EnhancedExamplePageState extends State<EnhancedExamplePage> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Enhanced Hexagon Features'),
      ),
      body: IndexedStack(
        index: _selectedIndex,
        children: const [
          FillStylesExample(),
          AnimationsExample(),
          SnakePatternsExample(),
          DataDrivenExample(),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.format_paint),
            label: 'Fill Styles',
          ),
          NavigationDestination(
            icon: Icon(Icons.animation),
            label: 'Animations',
          ),
          NavigationDestination(
            icon: Icon(Icons.auto_awesome),
            label: 'Snake Patterns',
          ),
          NavigationDestination(
            icon: Icon(Icons.dataset),
            label: 'Data Driven',
          ),
        ],
      ),
    );
  }
}

/// 填充样式示例
class FillStylesExample extends StatelessWidget {
  const FillStylesExample({super.key});

  @override
  Widget build(BuildContext context) {
    final hexWidgets = DataDrivenHexGenerator.generate(
      DataDrivenHexConfig<Map<String, dynamic>>(
        data: [
          {'name': 'Solid', 'type': 'solid'},
          {'name': 'Gradient', 'type': 'gradient'},
          {'name': 'Pattern', 'type': 'pattern'},
          {'name': 'Border', 'type': 'border'},
          {'name': 'Shadow', 'type': 'shadow'},
          {'name': 'Opacity', 'type': 'opacity'},
        ],
        pattern: HexLayoutPattern.honeycomb,
        startPosition: const Offset(350, 250),
        hexSize: 50,
        gap: 4.0,
        honeycombRings: 2,
        hexBuilder: (data, hex, index) {
          final type = data['type'] as String;

          HexFillStyle fillStyle;
          HexBorderStyle? borderStyle;
          List<BoxShadow>? shadows;
          double opacity = 1.0;

          switch (type) {
            case 'solid':
              fillStyle = SolidColorFill(Colors.blue.shade400);
              break;
            case 'gradient':
              fillStyle = GradientFill(
                LinearGradient(
                  colors: [Colors.purple, Colors.pink],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                ),
              );
              break;
            case 'pattern':
              fillStyle = PatternFill(
                Container(
                  decoration: BoxDecoration(
                    gradient: RadialGradient(
                      colors: [Colors.yellow, Colors.orange, Colors.red],
                    ),
                  ),
                ),
              );
              break;
            case 'border':
              fillStyle = SolidColorFill(Colors.white);
              borderStyle = HexBorderStyle(
                color: Colors.green,
                width: 4.0,
                join: StrokeJoin.round,
              );
              break;
            case 'shadow':
              fillStyle = SolidColorFill(Colors.cyan.shade300);
              shadows = [
                BoxShadow(
                  color: Colors.black.withOpacity(0.3),
                  blurRadius: 10,
                  offset: const Offset(2, 2),
                ),
              ];
              break;
            case 'opacity':
              fillStyle = SolidColorFill(Colors.red.shade400);
              opacity = 0.5;
              break;
            default:
              fillStyle = SolidColorFill(Colors.grey);
          }

          return EnhancedFreeHexWidget(
            id: hex.id,
            hexagon: hex,
            fillStyle: fillStyle,
            borderStyle: borderStyle ?? const HexBorderStyle.none(),
            shadows: shadows,
            opacity: opacity,
            child: Center(
              child: Text(
                data['name'],
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                  fontSize: 10,
                ),
                textAlign: TextAlign.center,
              ),
            ),
          );
        },
      ),
    );

    return Stack(
      children: hexWidgets
          .map(
            (w) => Positioned(
              left: w.hexagon.center.dx - w.hexagon.size,
              top: w.hexagon.center.dy - w.hexagon.size,
              child: SizedBox(
                width: w.hexagon.size * 2,
                height: w.hexagon.size * 2,
                child: EnhancedHexagonRenderer(config: w),
              ),
            ),
          )
          .toList(),
    );
  }
}

/// 动画示例
class AnimationsExample extends StatelessWidget {
  const AnimationsExample({super.key});

  @override
  Widget build(BuildContext context) {
    final hexWidgets = DataDrivenHexGenerator.generate(
      DataDrivenHexConfig<Map<String, dynamic>>(
        data: [
          {'name': 'None', 'animation': HexClickAnimation.none},
          {'name': 'Scale', 'animation': HexClickAnimation.scale},
          {'name': 'Ripple', 'animation': HexClickAnimation.ripple},
          {'name': 'Bounce', 'animation': HexClickAnimation.bounce},
          {'name': 'Glow', 'animation': HexClickAnimation.glow},
        ],
        pattern: HexLayoutPattern.ring,
        startPosition: const Offset(350, 280),
        hexSize: 55,
        gap: 3.0,
        hexBuilder: (data, hex, index) {
          return EnhancedFreeHexWidget(
            id: hex.id,
            hexagon: hex,
            fillStyle: SolidColorFill(Colors.teal.shade400),
            borderStyle: HexBorderStyle(
              color: Colors.teal.shade700,
              width: 2.0,
            ),
            clickAnimation: data['animation'] as HexClickAnimation,
            onTap: () {
              print('Tapped: ${data['name']}');
            },
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(Icons.touch_app, color: Colors.white, size: 20),
                  const SizedBox(height: 4),
                  Text(
                    data['name'],
                    style: const TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 10,
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );

    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            'Click hexagons to see different animations',
            style: Theme.of(context).textTheme.titleMedium,
          ),
        ),
        Expanded(
          child: Stack(
            children: hexWidgets
                .map(
                  (w) => Positioned(
                    left: w.hexagon.center.dx - w.hexagon.size,
                    top: w.hexagon.center.dy - w.hexagon.size,
                    child: SizedBox(
                      width: w.hexagon.size * 2,
                      height: w.hexagon.size * 2,
                      child: EnhancedHexagonRenderer(config: w),
                    ),
                  ),
                )
                .toList(),
          ),
        ),
      ],
    );
  }
}

/// 蛇形图案示例
class SnakePatternsExample extends StatefulWidget {
  const SnakePatternsExample({super.key});

  @override
  State<SnakePatternsExample> createState() => _SnakePatternsExampleState();
}

class _SnakePatternsExampleState extends State<SnakePatternsExample> {
  HexLayoutPattern _pattern = HexLayoutPattern.snake;
  double _bendFactor = 0.3;
  int _count = 12;
  bool _clockwise = true; // 生成顺序:true=顺时针,false=逆时针
  String _boundedDirection = 'right'; // bounded方向:up/down/left/right
  double _hexSize = 30.0; // 六边形大小

  @override
  Widget build(BuildContext context) {
    final colors = [
      Colors.red,
      Colors.orange,
      Colors.yellow,
      Colors.green,
      Colors.blue,
      Colors.indigo,
      Colors.purple,
    ];

    final hexWidgets = DataDrivenHexGenerator.generate(
      DataDrivenHexConfig<int>(
        data: List.generate(_count, (i) => i),
        pattern: _pattern,
        startPosition: const Offset(100, 100),
        hexSize: _hexSize,
        gap: 2.0,
        bendFactor: _bendFactor,
        clockwise: _clockwise,
        boundedDirection: _boundedDirection,
        bounds: const Rect.fromLTWH(50, 80, 600, 400),
        zigWidth: 3,
        zigHeight: 2,
        hexBuilder: (index, hex, _) {
          return EnhancedFreeHexWidget(
            id: hex.id,
            hexagon: hex,
            fillStyle: GradientFill(
              LinearGradient(
                colors: [
                  colors[index % colors.length],
                  colors[(index + 1) % colors.length],
                ],
              ),
            ),
            borderStyle: HexBorderStyle(color: Colors.white, width: 2.0),
            child: Center(
              child: Text(
                '$index',
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                  fontSize: 11,
                ),
              ),
            ),
          );
        },
      ),
    );

    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Pattern', style: Theme.of(context).textTheme.titleSmall),
              Wrap(
                spacing: 8,
                runSpacing: 8,
                children: [
                  ChoiceChip(
                    label: const Text('Snake'),
                    avatar: const Icon(Icons.waves, size: 18),
                    selected: _pattern == HexLayoutPattern.snake,
                    onSelected: (selected) {
                      if (selected)
                        setState(() => _pattern = HexLayoutPattern.snake);
                    },
                  ),
                  ChoiceChip(
                    label: const Text('Spiral'),
                    avatar: const Icon(Icons.refresh, size: 18),
                    selected: _pattern == HexLayoutPattern.spiral,
                    onSelected: (selected) {
                      if (selected)
                        setState(() => _pattern = HexLayoutPattern.spiral);
                    },
                  ),
                  ChoiceChip(
                    label: const Text('ZigZag'),
                    avatar: const Icon(Icons.trending_up, size: 18),
                    selected: _pattern == HexLayoutPattern.zigzag,
                    onSelected: (selected) {
                      if (selected)
                        setState(() => _pattern = HexLayoutPattern.zigzag);
                    },
                  ),
                  ChoiceChip(
                    label: const Text('Bounded'),
                    avatar: const Icon(Icons.border_outer, size: 18),
                    selected: _pattern == HexLayoutPattern.snakeBounded,
                    onSelected: (selected) {
                      if (selected)
                        setState(
                          () => _pattern = HexLayoutPattern.snakeBounded,
                        );
                    },
                  ),
                ],
              ),
              const SizedBox(height: 12),

              // 只在非边界模式下显示 Bend Factor
              if (_pattern != HexLayoutPattern.snakeBounded) ...[
                Text(
                  'Bend Factor: ${_bendFactor.toStringAsFixed(2)}',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                Slider(
                  value: _bendFactor,
                  min: 0.0,
                  max: 1.0,
                  divisions: 20,
                  onChanged: (value) {
                    setState(() {
                      _bendFactor = value;
                    });
                  },
                ),
              ],

              // 边界模式的特殊参数
              if (_pattern == HexLayoutPattern.snakeBounded) ...[
                Text(
                  'Generation Direction',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                const SizedBox(height: 8),
                Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    ChoiceChip(
                      label: const Text('Up'),
                      avatar: const Icon(Icons.arrow_upward, size: 18),
                      selected: _boundedDirection == 'up',
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _boundedDirection = 'up');
                        }
                      },
                    ),
                    ChoiceChip(
                      label: const Text('Down'),
                      avatar: const Icon(Icons.arrow_downward, size: 18),
                      selected: _boundedDirection == 'down',
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _boundedDirection = 'down');
                        }
                      },
                    ),
                    ChoiceChip(
                      label: const Text('Left'),
                      avatar: const Icon(Icons.arrow_back, size: 18),
                      selected: _boundedDirection == 'left',
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _boundedDirection = 'left');
                        }
                      },
                    ),
                    ChoiceChip(
                      label: const Text('Right'),
                      avatar: const Icon(Icons.arrow_forward, size: 18),
                      selected: _boundedDirection == 'right',
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _boundedDirection = 'right');
                        }
                      },
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                Text(
                  'Hexagon Size: ${_hexSize.toStringAsFixed(1)}',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                Slider(
                  value: _hexSize,
                  min: 15.0,
                  max: 50.0,
                  divisions: 35,
                  onChanged: (value) {
                    setState(() {
                      _hexSize = value;
                    });
                  },
                ),
                const SizedBox(height: 12),
                Text(
                  'Generation Order',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                const SizedBox(height: 8),
                Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    ChoiceChip(
                      label: const Text('Clockwise'),
                      avatar: const Icon(Icons.rotate_right, size: 18),
                      selected: _clockwise,
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _clockwise = true);
                        }
                      },
                    ),
                    ChoiceChip(
                      label: const Text('Counter-Clockwise'),
                      avatar: const Icon(Icons.rotate_left, size: 18),
                      selected: !_clockwise,
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _clockwise = false);
                        }
                      },
                    ),
                  ],
                ),
              ],

              const SizedBox(height: 12),
              Text(
                'Count: $_count',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              Slider(
                value: _count.toDouble(),
                min: 5,
                max: _pattern == HexLayoutPattern.snakeBounded ? 50 : 20,
                divisions: _pattern == HexLayoutPattern.snakeBounded ? 45 : 15,
                onChanged: (value) {
                  setState(() {
                    _count = value.toInt();
                  });
                },
              ),
            ],
          ),
        ),
        Expanded(
          child: Container(
            color: _pattern == HexLayoutPattern.snakeBounded
                ? Colors.grey.shade200
                : null,
            child: Stack(
              children: [
                // 显示边界矩形(仅在Bounded模式下)
                if (_pattern == HexLayoutPattern.snakeBounded)
                  Positioned(
                    left: 50,
                    top: 80,
                    child: Container(
                      width: 600,
                      height: 400,
                      decoration: BoxDecoration(
                        border: Border.all(
                          color: Colors.red.withOpacity(0.5),
                          width: 2,
                        ),
                      ),
                    ),
                  ),
                // 六边形
                ...hexWidgets.map(
                  (w) => Positioned(
                    left: w.hexagon.center.dx - w.hexagon.size,
                    top: w.hexagon.center.dy - w.hexagon.size,
                    child: SizedBox(
                      width: w.hexagon.size * 2,
                      height: w.hexagon.size * 2,
                      child: EnhancedHexagonRenderer(config: w),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

/// 数据驱动示例
class DataDrivenExample extends StatelessWidget {
  const DataDrivenExample({super.key});

  @override
  Widget build(BuildContext context) {
    // 模拟一些数据
    final userData = [
      {'name': 'Alice', 'score': 95, 'color': Colors.red},
      {'name': 'Bob', 'score': 87, 'color': Colors.blue},
      {'name': 'Carol', 'score': 92, 'color': Colors.green},
      {'name': 'David', 'score': 78, 'color': Colors.orange},
      {'name': 'Eve', 'score': 88, 'color': Colors.purple},
      {'name': 'Frank', 'score': 91, 'color': Colors.teal},
      {'name': 'Grace', 'score': 85, 'color': Colors.pink},
    ];

    final hexWidgets = DataDrivenHexGenerator.generate(
      DataDrivenHexConfig<Map<String, dynamic>>(
        data: userData,
        pattern: HexLayoutPattern.honeycomb,
        startPosition: const Offset(350, 280),
        hexSize: 50,
        gap: 3.0,
        honeycombRings: 2,
        hexBuilder: (data, hex, index) {
          final score = data['score'] as int;
          final name = data['name'] as String;
          final baseColor = data['color'] as Color;

          // 根据分数调整颜色亮度
          final opacity = (score / 100).clamp(0.5, 1.0);

          return EnhancedFreeHexWidget(
            id: hex.id,
            hexagon: hex,
            fillStyle: SolidColorFill(baseColor.withOpacity(opacity)),
            borderStyle: HexBorderStyle(
              color: baseColor.withOpacity(0.8),
              width: 3.0,
            ),
            clickAnimation: HexClickAnimation.bounce,
            shadows: [
              BoxShadow(
                color: baseColor.withOpacity(0.3),
                blurRadius: 8,
                offset: const Offset(0, 2),
              ),
            ],
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('$name scored $score!'),
                  duration: const Duration(seconds: 1),
                ),
              );
            },
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    name,
                    style: const TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 12,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '$score',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );

    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'User Scores (Click to see details)',
                style: Theme.of(context).textTheme.titleMedium,
              ),
              const SizedBox(height: 8),
              Text(
                'Each hexagon represents a user with their score. Color opacity indicates performance.',
                style: Theme.of(context).textTheme.bodySmall,
              ),
            ],
          ),
        ),
        Expanded(
          child: Stack(
            children: hexWidgets
                .map(
                  (w) => Positioned(
                    left: w.hexagon.center.dx - w.hexagon.size,
                    top: w.hexagon.center.dy - w.hexagon.size,
                    child: SizedBox(
                      width: w.hexagon.size * 2,
                      height: w.hexagon.size * 2,
                      child: EnhancedHexagonRenderer(config: w),
                    ),
                  ),
                )
                .toList(),
          ),
        ),
      ],
    );
  }
}
1
likes
145
points
19
downloads

Publisher

verified publisherpeegel.xyz

Weekly Downloads

A powerful Flutter package for creating hexagonal UI layouts with grid system, free layout, pattern generators, and boundary-aware generation.

Homepage
Repository (GitLab)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on hexagonal