Flutter路径绘制插件path_drawing的使用

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

Flutter路径绘制插件path_drawing的使用

path_drawing简介

path_drawing 是一个用于辅助创建和操作路径的Flutter库。它支持从SVG路径数据字符串解析Path,并将路径命令标准化为适合Flutter暴露的Path方法的形式。

[!NOTE] 该库的虚线路径功能依赖于flutter 0.3.6或更高版本。

未来计划添加的功能包括:

  • 裁剪路径(Trim paths)

使用示例

解析SVG路径字符串

你可以通过parseSvgPathData函数轻松地从SVG路径数据中解析出Path对象。例如,要解析一个三角形的路径:

import 'package:path_drawing/path_drawing.dart';

final trianglePath = parseSvgPathData('M150 0 L75 200 L225 200 Z');

创建CustomPainter

为了在屏幕上绘制这个路径,我们需要创建一个继承自CustomPainter的类,并重写其paintshouldRepaint方法。这里我们定义了一个FilledPathPainter来填充颜色并绘制指定的路径:

class FilledPathPainter extends CustomPainter {
  const FilledPathPainter({
    @required this.path,
    @required this.color,
  });

  final Path path;
  final Color color;

  @override
  bool shouldRepaint(FilledPathPainter oldDelegate) =>
      oldDelegate.path != path || oldDelegate.color != color;

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

  @override
  bool hitTest(Offset position) => path.contains(position);
}

在CustomPaint中使用

最后,我们将上述自定义的CustomPainter放入CustomPaint widget中进行显示:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => print('tap'),
      child: CustomPaint(
        painter: FilledPathPainter(
          path: trianglePath,
          color: Colors.blue,
        ),
      ),
    );
  }
}

完整Demo

下面是一个更完整的演示程序,它展示了如何在一个简单的Flutter应用中使用path_drawing库的不同特性,如路径裁剪、虚线效果等。

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

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late int index;
  late double _trimPercent;
  late PathTrimOrigin _trimOrigin;

  @override
  void initState() {
    super.initState();
    index = 0;
    _trimPercent = 0.2;
    _trimOrigin = PathTrimOrigin.begin;
  }

  String get currPath => paths[index];

  void nextPath() {
    setState(() => index = index >= paths.length - 1 ? 0 : index + 1);
  }

  void prevPath() {
    setState(() => index = index == 0 ? paths.length - 1 : index - 1);
  }

  void setTrimPercent(double value) {
    setState(() {
      _trimPercent = value;
    });
  }

  void toggleTrimOrigin(PathTrimOrigin? value) {
    setState(() {
      switch (_trimOrigin) {
        case PathTrimOrigin.begin:
          _trimOrigin = PathTrimOrigin.end;
          break;
        case PathTrimOrigin.end:
          _trimOrigin = PathTrimOrigin.begin;
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          bottom: const TabBar(
            tabs: <Tab>[
              Tab(text: 'Path Trim'),
              Tab(text: 'Path Dash'),
              Tab(text: 'Path Parse'),
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            Stack(
              children: <Widget>[
                CustomPaint(
                    painter: TrimPathPainter(_trimPercent, _trimOrigin)),
                Align(
                  alignment: Alignment.bottomCenter,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: <Widget>[
                      Slider(
                        value: _trimPercent,
                        onChanged: (double value) => setTrimPercent(value),
                      ),
                      RadioListTile<PathTrimOrigin>(
                        title: Text(PathTrimOrigin.begin.toString()),
                        value: PathTrimOrigin.begin,
                        groupValue: _trimOrigin,
                        onChanged: toggleTrimOrigin,
                      ),
                      RadioListTile<PathTrimOrigin>(
                        title: Text(PathTrimOrigin.end.toString()),
                        value: PathTrimOrigin.end,
                        groupValue: _trimOrigin,
                        onChanged: toggleTrimOrigin,
                      ),
                    ],
                  ),
                ),
              ],
            ),
            CustomPaint(painter: DashPathPainter()),
            Stack(
              children: <Widget>[
                CustomPaint(painter: PathTestPainter(currPath)),
                GestureDetector(
                  onTap: nextPath,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

const List<String> paths = <String>[
  // 这里可以放置多个SVG路径字符串...
  'm18 11.8a.41.41 0 0 1 .24.08l.59.43h.05.72a.4.4 0 0 1 .39.28l.22.69a.08.08 0 0 0 0 0l.58.43a.41.41 0 0 1 .15.45l-.22.68a.09.09 0 0 0 0 .07l.22.68a.4.4 0 0 1 -.15.46l-.58.42a.1.1 0 0 0 0 0l-.22.68a.41.41 0 0 1 -.38.29h-.79l-.58.43a.41.41 0 0 1 -.24.08.46.46 0 0 1 -.24-.08l-.58-.43h-.06-.72a.41.41 0 0 1 -.39-.28l-.22-.68a.1.1 0 0 0 0 0l-.58-.43a.42.42 0 0 1 -.15-.46l.23-.67v-.02l-.29-.68a.43.43 0 0 1 .15-.46l.58-.42a.1.1 0 0 0 0-.05l.27-.69a.42.42 0 0 1 .39-.28h.78l.58-.43a.43.43 0 0 1 .25-.09m0-1a1.37 1.37 0 0 0 -.83.27l-.34.25h-.43a1.42 1.42 0 0 0 -1.34 1l-.13.4-.35.25a1.42 1.42 0 0 0 -.51 1.58l.13.4-.13.4a1.39 1.39 0 0 0 .52 1.59l.34.25.13.4a1.41 1.41 0 0 0 1.34 1h.43l.34.26a1.44 1.44 0 0 0 .83.27 1.38 1.38 0 0 0 .83-.28l.35-.24h.43a1.4 1.4 0 0 0 1.33-1l.13-.4.35-.26a1.39 1.39 0 0 0 .51-1.57l-.13-.4.13-.41a1.4 1.4 0 0 0 -.51-1.56l-.35-.25-.13-.41a1.4 1.4 0 0 0 -1.34-1h-.42l-.34-.26a1.43 1.43 0 0 0 -.84-.28z',
  '''M 15 15.5 A 0.5 1.5 0 1 1  14,15.5 A 0.5 1.5 0 1 1  15 15.5 z''',
  // 更多路径...
];

final Paint black = Paint()
  ..color = Colors.black
  ..strokeWidth = 1.0
  ..style = PaintingStyle.stroke;

class TrimPathPainter extends CustomPainter {
  TrimPathPainter(this.percent, this.origin);

  final double percent;
  final PathTrimOrigin origin;

  final Path p = Path()
    ..moveTo(10.0, 10.0)
    ..lineTo(100.0, 100.0)
    ..quadraticBezierTo(125.0, 20.0, 200.0, 100.0);

  @override
  bool shouldRepaint(TrimPathPainter oldDelegate) =>
      oldDelegate.percent != percent;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPath(trimPath(p, percent, origin: origin), black);
  }
}

class DashPathPainter extends CustomPainter {
  final Path p = Path()
    ..moveTo(10.0, 10.0)
    ..lineTo(100.0, 100.0)
    ..quadraticBezierTo(125.0, 20.0, 200.0, 100.0)
    ..addRect(const Rect.fromLTWH(0.0, 0.0, 50.0, 50.0));

  @override
  bool shouldRepaint(DashPathPainter oldDelegate) => true;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPath(
        dashPath(
          p,
          dashArray: CircularIntervalList<double>(
            <double>[5.0, 2.5],
          ),
        ),
        black);
  }
}

class PathTestPainter extends CustomPainter {
  PathTestPainter(String path) : p = parseSvgPathData(path);

  final Path p;

  @override
  bool shouldRepaint(PathTestPainter oldDelegate) => true;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPath(p, black);
  }
}

以上代码提供了一个完整的例子,展示了如何利用path_drawing包来实现路径解析、裁剪以及虚线效果等功能。你可以根据自己的需求修改这些示例以适应具体的项目场景。


更多关于Flutter路径绘制插件path_drawing的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter路径绘制插件path_drawing的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用path_drawing插件来绘制路径的示例代码。path_drawing插件提供了将SVG路径数据渲染为Flutter绘图的功能。以下是一个简单的示例,展示了如何使用这个插件来绘制一个心形路径。

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

dependencies:
  flutter:
    sdk: flutter
  path_drawing: ^0.5.0  # 请检查最新版本号

然后,你可以使用以下代码在Flutter应用中绘制一个心形路径:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Path Drawing Example'),
        ),
        body: Center(
          child: CustomPaint(
            size: Size(300, 300),
            painter: HeartPathPainter(),
          ),
        ),
      ),
    );
  }
}

class HeartPathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.red
      ..style = PaintingStyle.fill;

    // 心形路径数据,使用SVG路径语法
    final String pathData = 'M160 290 Q200 220 240 290 T320 290 Q280 380 240 430 Q200 380 160 290';

    // 解析路径数据并绘制
    final Path path = parseSvgPathData(pathData);
    canvas.drawPath(path, paint);
  }

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

在这个示例中:

  1. 我们首先导入了必要的包,包括flutter/material.dartpath_drawing/path_drawing.dart
  2. MyApp类中,我们创建了一个简单的Flutter应用,其中包含一个Scaffold和一个居中的CustomPaint组件。
  3. CustomPaint组件使用HeartPathPainter类来绘制内容。
  4. HeartPathPainter类继承自CustomPainter并重写了paint方法。在paint方法中,我们定义了一个Paint对象来设置绘制的颜色和样式。
  5. 使用parseSvgPathData方法将SVG路径数据解析为Path对象。在这个例子中,我们使用了一个心形路径的SVG数据。
  6. 最后,使用canvas.drawPath方法将路径绘制到画布上。

这个示例展示了如何使用path_drawing插件来解析和绘制SVG路径数据。你可以根据需要修改路径数据来绘制不同的形状。

回到顶部