Flutter 自定义多边形小部件

230 阅读1分钟

需求

  1. 支持点击增加多边形点位(我的是最多八个,可自定义)
  2. 支持拖动点位进行移动成不规则多边形
  3. 支持检测当拖动的多边形存在边和边相交的情况则相交的边变成警告色

1.jpg

2.jpg

import 'dart:ui';

import 'package:camera/app_allocation/app_style.dart';
import 'package:camera/app_allocation/item_constants.dart';
import 'package:flutter/material.dart';

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

  @override
  State<CustomAreaUtils> createState() => _CustomAreaUtilsState();
}

class _CustomAreaUtilsState extends State<CustomAreaUtils> {
  List<Offset> points = [];

  int maxPoints = 8;

  @override
  void initState() {
    super.initState();
    points = [const Offset(50.0, 50.0), const Offset(300.0, 50.0), const Offset(300.0, 150.0), const Offset(50.0, 150.0)];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: ItemConstants.screenWidth,
              height: ItemConstants.cameraViewHeight,
              color: Colors.red,
              child: GestureDetector(
                onPanStart: (details) {
                  // 检查点击的位置是否在某个点上,如果是,则将其标记为选定的点,此时执行拖动代码
                  bool pointClicked = false;
                  for (int i = 0; i < points.length; i++) {
                    if ((points[i] - details.localPosition).distanceSquared < 100) {
                      pointClicked = true;
                      break;
                    }
                  }
                  // 如果未在点上点击,则添加新的点
                  if (!pointClicked && points.length < maxPoints) {
                    setState(() {
                      points = List.from(points)..add(details.localPosition);
                    });
                  }
                },
                onPanUpdate: (details) {
                  // 检查点击的位置是否在某个点上,如果是,则将其标记为选定的点,此时执行拖动代码
                  for (int i = 0; i < points.length; i++) {
                    if ((points[i] - details.localPosition).distanceSquared < 100) {
                      setState(() {
                        points[i] = details.localPosition;
                      });
                      break;
                    }
                  }
                },
                child: CustomPaint(painter: CustomAreaPainter(points)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class CustomAreaPainter extends CustomPainter {
  final List<Offset> points;

  CustomAreaPainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = AppStyle().getColor.mainColor
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.round;

    if (points.length > 1 && points.length <= 8) {
      canvas.drawPoints(PointMode.polygon, points, paint);
      if (points.length > 2) {
        canvas.drawLine(points.first, points.last, paint);
      }
    }

    for (int i = 0; i < points.length; i++) {
      canvas.drawCircle(points[i], 6.0, paint);
      TextSpan span = TextSpan(
        text: '${i + 1}',
        style: const TextStyle(color: Colors.white, fontSize: 12.0),
      );
      TextPainter tp = TextPainter(
        text: span,
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr,
      );
      tp.layout();
      tp.paint(canvas, Offset(points[i].dx - 4, points[i].dy - 8));
    }
  }

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