Flutter六边形网格图案生成插件hexpattern的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter六边形网格图案生成插件hexpattern的使用

Hex Pattern

hexpattern 是一个用于生成六边形网格图案的 Flutter 插件。它实现了 NUD(Nostr Uniform Design)规范中的 Hex Pattern。

示例

示例代码

import 'dart:async';
import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:string_validator/string_validator.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:statescope/statescope.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:nostr_core_dart/nostr.dart';
import 'package:web/web.dart' as web;

import 'package:hexpattern/hexpattern.dart';

const contentColumnWidth = 600.0;
const title = 'Hex Pattern';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    StateScope(
      creator: () => ThemeState(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    final themeState = context.watch<ThemeState>();
    return MaterialApp(
      title: title,
      theme: DemoTheme.light(),
      darkTheme: DemoTheme.dark(),
      themeMode: themeState.themeMode,
      debugShowCheckedModeBanner: false,
      home: const Demo(),
    );
  }
}

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

  @override
  State<Demo> createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  final scrollController = ScrollController();
  final textEditingController = TextEditingController(
      text: '3c7d12a6c2f71fe9ca2527216f529a137bb0f2eb018b18f30003933b9532013e');

  String? pubkey;

  @override
  void initState() {
    super.initState();
    validate();
    textEditingController.addListener(() {
      validate();
    });
    unawaited(_getPublicKey());
  }

  @override
  void dispose() {
    scrollController.dispose();
    textEditingController.dispose();
    super.dispose();
  }

  void validate() {
    final input = textEditingController.text.trim();
    if (input.startsWith('npub1')) {
      final decoded = Nip19.decodePubkey(input);
      if (decoded.isNotEmpty) {
        textEditingController.text = decoded;
        setState(() {});
        return;
      }
    }
    String? output;
    if (input.length == 64 && isHexadecimal(input)) {
      output = input;
    }
    setState(() {
      pubkey = output;
    });
  }

  Future<void> goToRepo() async {
    final parsed = Uri.parse('https://github.com/1l0/hexpattern');
    if (!await launchUrl(parsed)) {
      throw Exception('Could not launch ${parsed.path}');
    }
  }

  Future<void> _getPublicKey() async {
    final nostr = web.window.getProperty('nostr'.toJS).jsify() as JSObject;
    if (nostr.isUndefinedOrNull) {
      return;
    }
    nostr.callMethod(
        'on'.toJS,
        'accountChanged'.toJS,
        (() {
          final getPublicKey =
              nostr.callMethod<JSPromise<JSString>>('getPublicKey'.toJS).toDart;
          getPublicKey.then((pkjs) {
            final pkdart = pkjs.toDart;
            textEditingController.text = pkdart;
          });
        }).toJS);

    final getPublicKey =
        nostr.callMethod<JSPromise<JSString>>('getPublicKey'.toJS).toDart;
    final pkjs = await getPublicKey;
    final pkdart = pkjs.toDart;
    textEditingController.text = pkdart;
  }

  @override
  Widget build(BuildContext context) {
    final colScheme = Theme.of(context).colorScheme;
    final isDarkMode = colScheme.brightness == Brightness.dark;
    return Scaffold(
      backgroundColor: colScheme.surface,
      body: LayoutBuilder(builder: (context, constraints) {
        final height = constraints.maxWidth / 4;
        return Stack(
          alignment: AlignmentDirectional.topCenter,
          children: [
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Text(
                    title,
                    style: Theme.of(context).textTheme.headlineLarge,
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 20.0),
                  constraints: const BoxConstraints(maxWidth: 570.0),
                  child: TextField(
                    controller: textEditingController,
                    decoration:
                        const InputDecoration(hintText: 'Public key or npub'),
                    maxLines: 3,
                    minLines: 1,
                    style: const TextStyle(
                      fontSize: 13.9,
                    ),
                  ),
                ),
                if (pubkey == null && textEditingController.text.isNotEmpty)
                  Padding(
                    padding: const EdgeInsets.all(10.0),
                    child: Text(
                      'Invalid public key',
                      style: TextStyle(color: colScheme.error),
                    ),
                  ),
                if (pubkey != null)
                  HexPattern(
                    hexKey: pubkey!,
                    height: height,
                    edgeLetterLength: 1,
                  ),
                if (pubkey != null)
                  HexColor(
                    hexKey: pubkey!,
                  ),
              ],
            ),
            Row(
              children: [
                const Spacer(),
                Padding(
                  padding: const EdgeInsets.all(5.0),
                  child: Wrap(
                    direction: Axis.horizontal,
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 5.0,
                    runSpacing: 5.0,
                    children: [
                      IconButton(
                        onPressed: () {
                          final themeState = context.read<ThemeState>();
                          if (isDarkMode) {
                            themeState.toLight();
                            return;
                          }
                          themeState.toDark();
                        },
                        icon: isDarkMode
                            ? const FaIcon(Icons.light_mode)
                            : const FaIcon(Icons.dark_mode),
                      ),
                      IconButton(
                        onPressed: goToRepo,
                        icon: const FaIcon(FontAwesomeIcons.github),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ],
        );
      }),
    );
  }
}

class HexColor extends StatelessWidget {
  const HexColor({
    super.key,
    required this.hexKey,
    this.height = 20,
  });

  final String hexKey;
  final double height;

  @override
  Widget build(BuildContext context) {
    final color = HexConverter.hexToColor(hexKey);
    final rgb = color.toString().substring(10, 16);
    final hashed = '#$rgb';
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          hashed,
          style: TextStyle(
            color: color,
            fontWeight: FontWeight.bold,
            fontSize: height,
          ),
        ),
        IconButton(
          onPressed: () async {
            await Clipboard.setData(ClipboardData(text: hashed));
            if (context.mounted) {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('Copied color code'),
                ),
              );
            }
          },
          icon: const FaIcon(FontAwesomeIcons.copy),
        ),
      ],
    );
  }
}

class ThemeState extends ChangeNotifier {
  ThemeMode themeMode = ThemeMode.system;

  void toDark() {
    themeMode = ThemeMode.dark;
    notifyListeners();
  }

  void toLight() {
    themeMode = ThemeMode.light;
    notifyListeners();
  }
}

class DemoTheme {
  static ThemeData dark() {
    final td = ThemeData.from(
      colorScheme: const ColorScheme.dark(
        primary: Color.fromARGB(255, 255, 255, 255),
        secondary: Color.fromARGB(255, 110, 184, 221),
        onSecondaryContainer: Color.fromARGB(255, 255, 255, 255),
        tertiaryContainer: Color.fromARGB(255, 64, 64, 64),
        onTertiaryContainer: Color.fromARGB(255, 168, 168, 168),
        surface: Color.fromARGB(255, 29, 29, 29),
        surfaceBright: Color.fromARGB(255, 41, 41, 41),
        surfaceContainer: Color.fromARGB(255, 29, 29, 29),
        onSurface: Color.fromARGB(255, 216, 216, 216),
        onSurfaceVariant: Color.fromARGB(255, 137, 137, 137),
        outlineVariant: Color.fromARGB(255, 66, 66, 66),
      ),
    );
    return td;
  }

  static ThemeData light() {
    final td = ThemeData.from(
      colorScheme: const ColorScheme.light(
        primary: Color.fromARGB(255, 0, 0, 0),
        secondary: Color.fromARGB(255, 130, 130, 130),
        onSecondaryContainer: Color.fromARGB(255, 122, 122, 122),
        tertiaryContainer: Color.fromARGB(255, 247, 247, 247),
        onTertiaryContainer: Color.fromARGB(255, 124, 124, 124),
        surface: Color.fromARGB(255, 255, 255, 255),
        surfaceBright: Color.fromARGB(255, 255, 255, 255),
        surfaceContainer: Color.fromARGB(255, 255, 255, 255),
        onSurface: Color.fromARGB(255, 0, 0, 0),
        onSurfaceVariant: Color.fromARGB(255, 128, 128, 128),
        outlineVariant: Color.fromARGB(255, 229, 229, 229),
      ),
    );
    return td;
  }
}

说明

在上述代码中,我们展示了如何使用 hexpattern 插件来生成六边形网格图案。具体步骤如下:

  1. 导入必要的包

    import 'package:hexpattern/hexpattern.dart';
    
  2. 初始化应用

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(
        StateScope(
          creator: () => ThemeState(),
          child: const MyApp(),
        ),
      );
    }
    
  3. 创建主应用组件

    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        final themeState = context.watch<ThemeState>();
        return MaterialApp(
          title: title,
          theme: DemoTheme.light(),
          darkTheme: DemoTheme.dark(),
          themeMode: themeState.themeMode,
          debugShowCheckedModeBanner: false,
          home: const Demo(),
        );
      }
    }
    
  4. 创建主页面组件

    class Demo extends StatefulWidget {
      const Demo({super.key});
    
      @override
      State<Demo> createState() => _DemoState();
    }
    
  5. 实现状态管理逻辑

    class _DemoState extends State<Demo> {
      final scrollController = ScrollController();
      final textEditingController = TextEditingController(
          text: '3c7d12a6c2f71fe9ca2527216f529a137bb0f2eb018b18f30003933b9532013e');
    
      String? pubkey;
    
      @override
      void initState() {
        super.initState();
        validate();
        textEditingController.addListener(() {
          validate();
        });
        unawaited(_getPublicKey());
      }
    
      @override
      void dispose() {
        scrollController.dispose();
        textEditingController.dispose();
        super.dispose();
      }
    
      void validate() {
        final input = textEditingController.text.trim();
        if (input.startsWith('npub1')) {
          final decoded = Nip19.decodePubkey(input);
          if (decoded.isNotEmpty) {
            textEditingController.text = decoded;
            setState(() {});
            return;
          }
        }
        String? output;
        if (input.length == 64 && isHexadecimal(input)) {
          output = input;
        }
        setState(() {
          pubkey = output;
        });
      }
    
      Future<void> goToRepo() async {
        final parsed = Uri.parse('https://github.com/1l0/hexpattern');
        if (!await launchUrl(parsed)) {
          throw Exception('Could not launch ${parsed.path}');
        }
      }
    
      Future<void> _getPublicKey() async {
        final nostr = web.window.getProperty('nostr'.toJS).jsify() as JSObject;
        if (nostr.isUndefinedOrNull) {
          return;
        }
        nostr.callMethod(
            'on'.toJS,
            'accountChanged'.toJS,
            (() {
              final getPublicKey =
                  nostr.callMethod<JSPromise<JSString>>('getPublicKey'.toJS).toDart;
              getPublicKey.then((pkjs) {
                final pkdart = pkjs.toDart;
                textEditingController.text = pkdart;
              });
            }).toJS);
    
        final getPublicKey =
            nostr.callMethod<JSPromise<JSString>>('getPublicKey'.toJS).toDart;
        final pkjs = await getPublicKey;
        final pkdart = pkjs.toDart;
        textEditingController.text = pkdart;
      }
    
      @override
      Widget build(BuildContext context) {
        final colScheme = Theme.of(context).colorScheme;
        final isDarkMode = colScheme.brightness == Brightness.dark;
        return Scaffold(
          backgroundColor: colScheme.surface,
          body: LayoutBuilder(builder: (context, constraints) {
            final height = constraints.maxWidth / 4;
            return Stack(
              alignment: AlignmentDirectional.topCenter,
              children: [
                Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Padding(
                      padding: const EdgeInsets.all(20.0),
                      child: Text(
                        title,
                        style: Theme.of(context).textTheme.headlineLarge,
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 20.0),
                      constraints: const BoxConstraints(maxWidth: 570.0),
                      child: TextField(
                        controller: textEditingController,
                        decoration:
                            const InputDecoration(hintText: 'Public key or npub'),
                        maxLines: 3,
                        minLines: 1,
                        style: const TextStyle(
                          fontSize: 13.9,
                        ),
                      ),
                    ),
                    if (pubkey == null && textEditingController.text.isNotEmpty)
                      Padding(
                        padding: const EdgeInsets.all(10.0),
                        child: Text(
                          'Invalid public key',
                          style: TextStyle(color: colScheme.error),
                        ),
                      ),
                    if (pubkey != null)
                      HexPattern(
                        hexKey: pubkey!,
                        height: height,
                        edgeLetterLength: 1,
                      ),
                    if (pubkey != null)
                      HexColor(
                        hexKey: pubkey!,
                      ),
                  ],
                ),
                Row(
                  children: [
                    const Spacer(),
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Wrap(
                        direction: Axis.horizontal,
                        crossAxisAlignment: WrapCrossAlignment.center,
                        spacing: 5.0,
                        runSpacing: 5.0,
                        children: [
                          IconButton(
                            onPressed: () {
                              final themeState = context.read<ThemeState>();
                              if (isDarkMode) {
                                themeState.toLight();
                                return;
                              }
                              themeState.toDark();
                            },
                            icon: isDarkMode
                                ? const FaIcon(Icons.light_mode)
                                : const FaIcon(Icons.dark_mode),
                          ),
                          IconButton(
                            onPressed: goToRepo,
                            icon: const FaIcon(FontAwesomeIcons.github),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ],
            );
          }),
        );
      }
    }
    
  6. 定义颜色转换器

    class HexColor extends StatelessWidget {
      const HexColor({
        super.key,
        required this.hexKey,
        this.height = 20,
      });
    
      final String hexKey;
      final double height;
    
      @override
      Widget build(BuildContext context) {
        final color = HexConverter.hexToColor(hexKey);
        final rgb = color.toString().substring(10, 16);
        final hashed = '#$rgb';
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              hashed,
              style: TextStyle(
                color: color,
                fontWeight: FontWeight.bold,
                fontSize: height,
              ),
            ),
            IconButton(
              onPressed: () async {
                await Clipboard.setData(ClipboardData(text: hashed));
                if (context.mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Copied color code'),
                    ),
                  );
                }
              },
              icon: const FaIcon(FontAwesomeIcons.copy),
            ),
          ],
        );
      }
    }
    
  7. 定义主题状态管理

    class ThemeState extends ChangeNotifier {
      ThemeMode themeMode = ThemeMode.system;
    
      void toDark() {
        themeMode = ThemeMode.dark;
        notifyListeners();
      }
    
      void toLight() {
        themeMode = ThemeMode.light;
        notifyListeners();
      }
    }
    
  8. 定义主题

    class DemoTheme {
      static ThemeData dark() {
        final td = ThemeData.from(
          colorScheme: const ColorScheme.dark(
            primary: Color.fromARGB(255, 255, 255, 255),
            secondary: Color.fromARGB(255, 110, 184, 221),
            onSecondaryContainer: Color.fromARGB(255, 255, 255, 255),
            tertiaryContainer: Color.fromARGB(255, 64, 64, 64),
            onTertiaryContainer: Color.fromARGB(255, 168, 168, 168),
            surface: Color.fromARGB(255, 29, 29, 29),
            surfaceBright: Color.fromARGB(255, 41, 41, 41),
            surfaceContainer: Color.fromARGB(255, 29, 29, 29),
            onSurface: Color.fromARGB(255, 216, 216, 216),
            onSurfaceVariant: Color.fromARGB(255, 137, 137, 137),
            outlineVariant: Color.fromARGB(255, 66, 66, 66),
          ),
        );
        return td;
      }
    
      static ThemeData light() {
        final td = ThemeData.from(
          colorScheme: const ColorScheme.light(
            primary: Color.fromARGB(255, 0, 0, 0),
            secondary: Color.fromARGB(255, 130, 130, 130),
            onSecondaryContainer: Color.fromARGB(255, 122, 122, 122),
            tertiaryContainer: Color.fromARGB(255, 247, 247, 247),
            onTertiaryContainer: Color.fromARGB(255, 124, 124, 124),
            surface: Color.fromARGB(255, 255, 255, 255),
            surfaceBright: Color.fromARGB(255, 255, 255, 255),
            surfaceContainer: Color.fromARGB(255, 255, 255, 255),
            onSurface: Color.fromARGB(255, 0, 0, 0),
            onSurfaceVariant: Color.fromARGB(255, 128, 128, 128),
            outlineVariant: Color.fromARGB(255, 229, 229, 229),
          ),
        );
        return td;
      }
    }
    

更多关于Flutter六边形网格图案生成插件hexpattern的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter六边形网格图案生成插件hexpattern的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter插件hexpattern来生成六边形网格图案的示例代码。这个插件可以帮助你在Flutter应用中快速生成六边形网格布局。

首先,确保你已经在pubspec.yaml文件中添加了hexpattern依赖:

dependencies:
  flutter:
    sdk: flutter
  hexpattern: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

接下来是一个完整的Flutter应用示例,展示如何使用hexpattern插件来生成和显示六边形网格图案:

import 'package:flutter/material.dart';
import 'package:hexpattern/hexpattern.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'HexPattern Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HexPatternExample(),
    );
  }
}

class HexPatternExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('HexPattern Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: CustomPaint(
          size: Size(double.infinity, double.infinity),
          painter: HexPatternPainter(
            hexagonSize: 50.0,
            padding: 10.0,
            color: Colors.blue,
            borderColor: Colors.black,
            borderWidth: 2.0,
          ),
        ),
      ),
    );
  }
}

class HexPatternPainter extends CustomPainter {
  final double hexagonSize;
  final double padding;
  final Color color;
  final Color borderColor;
  final double borderWidth;

  HexPatternPainter({
    required this.hexagonSize,
    required this.padding,
    required this.color,
    required this.borderColor,
    required this.borderWidth,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    final BorderPaint borderPaint = Paint()
      ..color = borderColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth;

    final double hexWidth = hexagonSize * 1.5;
    final double hexHeight = hexagonSize * Math.sqrt(3);

    double startX = padding;
    double startY = padding + hexHeight / 2;

    for (int row = 0; row < (size.height - padding * 2) / hexHeight; row++) {
      for (int col = 0; col < (size.width - padding * 2) / hexWidth; col++) {
        final Path path = Path();
        path.moveTo(startX + col * hexWidth, startY - row * hexHeight / 2);
        for (int i = 0; i < 6; i++) {
          path.lineTo(
            startX + col * hexWidth + hexagonSize * (Math.cos(Math.PI / 3 * i)),
            startY - row * hexHeight / 2 + hexagonSize * (Math.sin(Math.PI / 3 * i)),
          );
        }
        path.close();

        canvas.drawPath(path, paint);
        canvas.drawPath(path, borderPaint);
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

注意:虽然上面的代码实现了六边形网格的绘制,但它并没有直接使用hexpattern插件,因为hexpattern插件的具体API可能会随版本变化,且其内部已经封装好了六边形的绘制逻辑。如果你希望直接使用hexpattern插件,可以参考其官方文档和示例代码,通常会更简洁。以下是一个假设性的使用hexpattern插件的示例(具体API需参考插件文档):

import 'package:flutter/material.dart';
import 'package:hexpattern/hexpattern.dart'; // 假设插件提供了HexPatternWidget

// ...

class HexPatternExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('HexPattern Example'),
      ),
      body: HexPatternWidget(
        hexagonSize: 50.0,
        color: Colors.blue,
        borderColor: Colors.black,
        borderWidth: 2.0,
      ),
    );
  }
}

请查阅hexpattern插件的最新文档和示例代码,以获取准确的API使用方法和参数配置。如果插件提供了HexPatternWidget或其他类似的封装好的组件,直接使用它们会更为简便。

回到顶部