Flutter形状编辑插件shape_editor的使用
Flutter形状编辑插件shape_editor的使用
shape_editor
示例应用地址: Demo App 示例 (示例源代码)
开始使用
使用 DiagramEditor
小部件:
DiagramEditor(
diagramEditorContext: DiagramEditorContext(
policySet: myPolicySet,
),
),
myPolicySet
是一个由混合(mixins)组成的类,例如:
class MyPolicySet extends PolicySet
with
MyInitPolicy,
CanvasControlPolicy,
LinkControlPolicy,
LinkJointControlPolicy,
LinkAttachmentRectPolicy {}
MyInitPolicy
可以如下定义:
mixin MyInitPolicy implements InitPolicy {
[@override](/user/override)
initializeDiagramEditor() {
canvasWriter.state.setCanvasColor(Colors.grey);
}
}
在 MyCanvasPolicy
的 onCanvasTapUp(TapUpDetails details)
函数中,如果没有任何组件被选中,则添加一个新的组件。
mixin MyCanvasPolicy implements CanvasPolicy, CustomPolicy {
[@override](/user/override)
onCanvasTapUp(TapUpDetails details) async {
canvasWriter.model.hideAllLinkJoints();
if (selectedComponentId != null) {
hideComponentHighlight(selectedComponentId);
} else {
canvasWriter.model.addComponent(
ComponentData(
size: Size(96, 72),
position: canvasReader.state.fromCanvasCoordinates(details.localPosition),
data: MyComponentData(),
),
);
}
}
}
可以实现并添加到策略集中的几种编辑器策略包括:
InitPolicy
CanvasPolicy
ComponentPolicy
ComponentDesignPolicy
LinkPolicy
LinkJointPolicy
LinkAttachmentPolicy
LinkWidgetsPolicy
CanvasWidgetsPolicy
ComponentWidgetsPolicy
一些已经实现并准备使用的策略包括:
CanvasControlPolicy
LinkControlPolicy
LinkJointControlPolicy
LinkAttachmentRectPolicy
这些策略的使用方法可以在文档中找到。更多例子请参见上面的链接。
示例代码
main.dart
import 'dart:math' as math;
import 'package:shape_editor/shape_editor.dart';
import 'package:flutter/material.dart';
void main() => runApp(const DiagramApp());
class DiagramApp extends StatefulWidget {
const DiagramApp({Key? key}) : super(key: key);
[@override](/user/override)
_DiagramAppState createState() => _DiagramAppState();
}
class _DiagramAppState extends State<DiagramApp> {
MyPolicySet myPolicySet = MyPolicySet();
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Container(color: Colors.grey),
Padding(
padding: const EdgeInsets.all(16),
child: DiagramEditor(
diagramEditorContext:
DiagramEditorContext(policySet: myPolicySet),
),
),
Padding(
padding: const EdgeInsets.all(0),
child: Row(
children: [
ElevatedButton(
onPressed: () => myPolicySet.deleteAllComponents(),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('删除所有组件')),
const Spacer(),
ElevatedButton(
onPressed: () => myPolicySet.serialize(),
child: const Text('序列化')),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => myPolicySet.deserialize(),
child: const Text('反序列化')),
],
),
),
],
),
),
),
);
}
}
// 自定义组件数据,你可以将其分配给组件的动态数据属性。
class MyComponentData {
MyComponentData();
bool isHighlightVisible = false;
Color color =
Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
void showHighlight() {
isHighlightVisible = true;
}
void hideHighlight() {
isHighlightVisible = false;
}
// 用于反序列化图表的函数。必须传递给 `canvasWriter.model.deserializeDiagram` 才能正确反序列化。
MyComponentData.fromJson(Map<String, dynamic> json)
: isHighlightVisible = json['highlight'],
color = Color(int.parse(json['color'], radix: 16));
// 用于图表序列化的函数。例如保存到文件。
Map<String, dynamic> toJson() => {
'highlight': isHighlightVisible,
'color': color.toString().split('(0x')[1].split(')')[0],
};
}
// 一组由混合组成的策略。有一些自定义策略实现,还有一些由 diagram_editor 库定义的策略。
class MyPolicySet extends PolicySet
with
MyInitPolicy,
MyComponentDesignPolicy,
MyCanvasPolicy,
MyComponentPolicy,
CustomPolicy,
//
LinkControlPolicy,
LinkJointControlPolicy,
LinkAttachmentRectPolicy {}
// 你可以在其中初始化画布或你的图表(例如加载现有的图表)。
mixin MyInitPolicy implements InitPolicy {
[@override](/user/override)
initializeDiagramEditor() {
canvasWriter.state.setCanvasColor(Colors.grey[300]!);
}
}
// 这是你定义组件设计的地方。
// 使用 `switch` 在 `componentData.type` 或 `componentData.data` 上来定义不同的组件设计。
mixin MyComponentDesignPolicy implements ComponentDesignPolicy {
[@override](/user/override)
Widget showComponentBody(ComponentData componentData, double scale) {
return Container(
decoration: BoxDecoration(
color: (componentData.data as MyComponentData).color,
border: Border.all(
width: 2,
color: (componentData.data as MyComponentData).isHighlightVisible
? Colors.pink
: Colors.black,
),
),
child: const Center(child: Text('组件')),
);
}
}
// 你可以在这里覆盖任何手势的行为。
// 注意,它还实现了 CustomPolicy,在这里可以定义和使用自己的变量和函数。
mixin MyCanvasPolicy implements CanvasPolicy, CustomPolicy {
[@override](/user/override)
onCanvasTapUp(TapUpDetails details) {
canvasWriter.model.hideAllLinkJoints();
if (selectedComponentId != null) {
hideComponentHighlight(selectedComponentId);
} else {
canvasWriter.model.addComponent(
ComponentData(
size: const Size(96, 72),
position:
canvasReader.state.fromCanvasCoordinates(details.localPosition),
data: MyComponentData(),
),
);
}
}
}
// 定义组件行为的混合。在这个例子中,它定义了移动、高亮和连接两个组件。
mixin MyComponentPolicy implements ComponentPolicy, CustomPolicy {
// 用于计算移动组件时的偏移量。
late Offset lastFocalPoint;
[@override](/user/override)
onComponentTap(String componentId) {
canvasWriter.model.hideAllLinkJoints();
bool connected = connectComponents(selectedComponentId, componentId);
hideComponentHighlight(selectedComponentId);
if (!connected) {
highlightComponent(componentId);
}
}
[@override](/user/override)
onComponentLongPress(String componentId) {
hideComponentHighlight(selectedComponentId);
canvasWriter.model.hideAllLinkJoints();
canvasWriter.model.removeComponent(componentId);
}
[@override](/user/override)
onComponentScaleStart(componentId, details) {
lastFocalPoint = details.localFocalPoint;
}
[@override](/user/override)
onComponentScaleUpdate(componentId, details) {
Offset positionDelta = details.localFocalPoint - lastFocalPoint;
canvasWriter.model.moveComponent(componentId, positionDelta);
lastFocalPoint = details.localFocalPoint;
}
// 测试是否可以连接两个组件,如果可以,则连接它们。
bool connectComponents(String? sourceComponentId, String? targetComponentId) {
if (sourceComponentId == null || targetComponentId == null) {
return false;
}
// 检查是否为同一组件
if (sourceComponentId == targetComponentId) {
return false;
}
// 检查两个组件之间的连接是否已存在(单向)
if (canvasReader.model.getComponent(sourceComponentId).connections.any(
(connection) =>
(connection is ConnectionOut) &&
(connection.otherComponentId == targetComponentId))) {
return false;
}
// 连接两个组件(创建链接),你可以通过 `LinkStyle` 定义链接的设计。
canvasWriter.model.connectTwoComponents(
sourceComponentId: sourceComponentId,
targetComponentId: targetComponentId,
linkStyle: LinkStyle(
arrowType: ArrowType.pointedArrow,
lineWidth: 1.5,
backArrowType: ArrowType.centerCircle,
),
);
return true;
}
}
// 你可以创建自己的 Policy 来定义自己的变量和函数,并使用 canvasReader 和 canvasWriter。
mixin CustomPolicy implements PolicySet {
String? selectedComponentId;
String serializedDiagram = '{"components": [], "links": []}';
void highlightComponent(String componentId) {
canvasReader.model.getComponent(componentId).data.showHighlight();
canvasReader.model.getComponent(componentId).updateComponent();
selectedComponentId = componentId;
}
void hideComponentHighlight(String? componentId) {
if (componentId != null) {
canvasReader.model.getComponent(componentId).data.hideHighlight();
canvasReader.model.getComponent(componentId).updateComponent();
selectedComponentId = null;
}
}
void deleteAllComponents() {
selectedComponentId = null;
canvasWriter.model.removeAllComponents();
}
// 将图表保存为 JSON 格式的字符串。
void serialize() {
serializedDiagram = canvasReader.model.serializeDiagram();
}
// 从 JSON 格式加载图表。谨慎操作,为了避免不稳定状态,移除之前的图表(ID 冲突可能发生)。
void deserialize() {
canvasWriter.model.removeAllComponents();
canvasWriter.model.deserializeDiagram(
serializedDiagram,
decodeCustomComponentData: (json) => MyComponentData.fromJson(json),
decodeCustomLinkData: null,
);
}
}
更多关于Flutter形状编辑插件shape_editor的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复