flutter CustomPainter - 如何在线路径中切出一个洞

flutter CustomPainter - how to cut out a hole in line path

我有一个 CustomPaint 画了一个椭圆形。

我想在特定位置切出一个孔,我还不知道它是如何工作的。

我试过了:

canvas.drawPath(
      Path.combine(PathOperation.difference, ovalPath, holePath),
      ovalPaint,
    );

但这并没有解决问题,而是给了我以下结果:

但这就是我想要实现的:

这个椭圆形只是一个例子,“真正的”定制油漆会变得更加复杂,我需要的不仅仅是一个切口。所以只画几行不是一个选择。我想先定义路径,然后应用剪切(甚至反向剪切)来获得孔。

这可能吗?

这是我所拥有的完整工作示例:

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

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: const Scaffold(
        body: Center(
          child: OvalCustomPaint(),
        ),
      ),
    );
  }
}

class OvalCustomPaint extends StatelessWidget {
  const OvalCustomPaint({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: LayoutBuilder(
        builder: (context, constraints) {
          return Center(
            child: CustomPaint(
              painter: _Painter(),
              child: SizedBox(
                width: constraints.maxWidth,
                height: constraints.maxHeight,
              ),
            ),
          );
        },
      ),
    );
  }
}

class _Painter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    const curveRadius = 50.0;
    const legLength = 150.0;
    canvas.rotate(pi/2);


    final ovalPaint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 2.5
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    const fixPoint = Offset.zero;

    //* OVAL LINE
    final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
    ovalPath.relativeArcToPoint(
      const Offset(curveRadius * 2, 0),
      radius: const Radius.circular(curveRadius),
    );
    ovalPath.relativeLineTo(0, legLength);
    ovalPath.relativeArcToPoint(
      const Offset(-curveRadius * 2, 0),
      radius: const Radius.circular(curveRadius),
    );
    ovalPath.relativeLineTo(0, -legLength);


    //* CLP HOLE
    final holePath = Path();
    holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);

    
    canvas.drawPath(
      Path.combine(PathOperation.difference, ovalPath, holePath),
      ovalPaint,
    );
  }

  @override
  bool shouldRepaint(_Painter oldDelegate) => false;

}

你可以控制线的长度。

ovalPath.relativeArcToPoint(
  const Offset(-curveRadius * 2, 0),
  radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength + (13 * 2)); //here based on radius

canvas.drawPath(
  ovalPath,
  ovalPaint,
);

好的,我找到了解决方案。

我创建了一个大小为 CustomPainter 区域的矩形路径,并用切口孔路径建立了差异。

创建了一个剪切模板:

    final rectWithCutout = Path.combine(
        PathOperation.difference,
        Path()
          ..addRect(
            Rect.fromCenter(
              center: Offset.zero,
              width: size.width,
              height: size.height,
            ),
          ),
        holePath);

我可以将 canvas 剪辑为:canvas.clipPath(rectWithCutout);

最终结果:

这又是完整的工作代码示例:

import 'dart:math';

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: const Scaffold(
        body: Center(
          child: OvalCustomPaint(),
        ),
      ),
    );
  }
}

class OvalCustomPaint extends StatelessWidget {
  const OvalCustomPaint({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: LayoutBuilder(
        builder: (context, constraints) {
          return Center(
            child: CustomPaint(
              painter: _Painter(),
              child: SizedBox(
                width: constraints.maxWidth,
                height: constraints.maxHeight,
              ),
            ),
          );
        },
      ),
    );
  }
}

class _Painter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    const curveRadius = 40.0;
    const legLength = 130.0;
    canvas.rotate(pi / 2);

    final ovalPaint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 2.5
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    const fixPoint = Offset.zero;

    //* OVAL LINE
    final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
    ovalPath.relativeArcToPoint(
      const Offset(curveRadius * 2, 0),
      radius: const Radius.circular(curveRadius),
    );
    ovalPath.relativeLineTo(0, legLength);
    ovalPath.relativeArcToPoint(
      const Offset(-curveRadius * 2, 0),
      radius: const Radius.circular(curveRadius),
    );
    ovalPath.relativeLineTo(0, -legLength);

    //* CLIP HOLE
    final holePath = Path();
    holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);

    final rectWithCutout = Path.combine(
        PathOperation.difference,
        Path()
          ..addRect(
            Rect.fromCenter(
              center: Offset.zero,
              width: size.width,
              height: size.height,
            ),
          ),
        holePath);

    canvas.clipPath(rectWithCutout);

    canvas.drawPath(
      ovalPath,
      ovalPaint,
    );
  }

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