Flutter路径绘制插件path_drawing的使用
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
的类,并重写其paint
和shouldRepaint
方法。这里我们定义了一个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
更多关于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;
}
}
在这个示例中:
- 我们首先导入了必要的包,包括
flutter/material.dart
和path_drawing/path_drawing.dart
。 - 在
MyApp
类中,我们创建了一个简单的Flutter应用,其中包含一个Scaffold
和一个居中的CustomPaint
组件。 CustomPaint
组件使用HeartPathPainter
类来绘制内容。HeartPathPainter
类继承自CustomPainter
并重写了paint
方法。在paint
方法中,我们定义了一个Paint
对象来设置绘制的颜色和样式。- 使用
parseSvgPathData
方法将SVG路径数据解析为Path
对象。在这个例子中,我们使用了一个心形路径的SVG数据。 - 最后,使用
canvas.drawPath
方法将路径绘制到画布上。
这个示例展示了如何使用path_drawing
插件来解析和绘制SVG路径数据。你可以根据需要修改路径数据来绘制不同的形状。