Flutter形状编辑插件shape_editor的使用

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

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);
  }
}

MyCanvasPolicyonCanvasTapUp(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 回复

更多关于Flutter形状编辑插件shape_editor的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter中的shape_editor插件的示例代码。这个插件允许用户在应用内编辑和创建形状。由于shape_editor插件的具体实现和API可能会随时间变化,以下代码是一个假设性的示例,旨在展示如何使用一个类似的插件进行形状编辑。

首先,你需要在你的pubspec.yaml文件中添加shape_editor依赖项(注意:实际插件名和版本号需要根据实际可用的插件来确定):

dependencies:
  flutter:
    sdk: flutter
  shape_editor: ^x.y.z  # 替换为实际版本号

然后,你可以在你的Flutter应用中如下使用shape_editor插件:

import 'package:flutter/material.dart';
import 'package:shape_editor/shape_editor.dart'; // 假设插件提供这样的导入路径

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Shape Editor Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ShapeEditorScreen(),
    );
  }
}

class ShapeEditorScreen extends StatefulWidget {
  @override
  _ShapeEditorScreenState createState() => _ShapeEditorScreenState();
}

class _ShapeEditorScreenState extends State<ShapeEditorScreen> {
  final ShapeEditorController _controller = ShapeEditorController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shape Editor Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Expanded(
              child: ShapeEditor(
                controller: _controller,
                onShapeChanged: (newShape) {
                  // 处理形状变化,例如保存到状态或进行其他处理
                  print('Shape changed: $newShape');
                },
              ),
            ),
            ElevatedButton(
              onPressed: () {
                // 示例:获取当前形状并处理
                final currentShape = _controller.currentShape;
                // 这里可以根据需要对currentShape进行处理
                print('Current Shape: $currentShape');
              },
              child: Text('Get Current Shape'),
            ),
          ],
        ),
      ),
    );
  }
}

// 假设ShapeEditorController和Shape类型由插件提供
// 这些类型和方法需要根据实际插件的API进行调整
class ShapeEditorController {
  Shape? get currentShape => /* 从插件状态获取当前形状 */;
  
  // 其他控制器方法...
}

class Shape {
  // 形状的属性,例如路径、颜色等
  // 这些属性需要根据实际插件的API进行调整
}

请注意,上述代码是一个假设性的示例,实际的shape_editor插件可能有不同的API和组件。你应该查阅该插件的官方文档或源代码以获取准确的API和使用方法。

此外,由于Flutter社区和插件生态系统非常活跃,新的插件和更新可能随时出现。因此,确保你使用的是最新版本的插件,并参考最新的官方文档。如果shape_editor插件不存在或不再维护,你可能需要寻找其他类似的插件或自己实现形状编辑功能。

回到顶部