Flutter 颤振有向图。我可以将CustomPainter类与自定义小部件一起使用吗?

Flutter 颤振有向图。我可以将CustomPainter类与自定义小部件一起使用吗?,flutter,dart,flutter-layout,flutter-canvas,Flutter,Dart,Flutter Layout,Flutter Canvas,我想用flatter构建一个有向图,如下图所示。 我不知道从哪里开始。我在网上搜索没有成功。这种图形需要哪些算法? 我试图用自定义的painter类构建这个图。我不知道如何在自定义画师类中使用自定义小部件。(例如,带有人物图片和文本的矩形)。 我只能画直线和直线。。。 缩放和平移我想我可以用GestureDetector类。 图表应该是可动态定制的 CustomPainter似乎有些过分了。使用堆栈。您需要首先计算每个节点或连接的位置和大小,然后使用定位小部件放置它们。您需要拆分任务 使层缩放和

我想用flatter构建一个有向图,如下图所示。 我不知道从哪里开始。我在网上搜索没有成功。这种图形需要哪些算法? 我试图用自定义的painter类构建这个图。我不知道如何在自定义画师类中使用自定义小部件。(例如,带有人物图片和文本的矩形)。 我只能画直线和直线。。。 缩放和平移我想我可以用GestureDetector类。 图表应该是可动态定制的


CustomPainter似乎有些过分了。使用堆栈。您需要首先计算每个节点或连接的位置和大小,然后使用定位小部件放置它们。

您需要拆分任务

  • 使层缩放和移动整个场景,可以使用 带有onScale事件+Transform.scale小部件的GestureDetector小部件, (检查zoom_小部件包)
  • 使单个项目可拖动。使用GestureDetector+onPan事件
  • 使用CustomPainter在元素之间绘制连接线。我用直线表示主要逻辑
  • 。。添加额外逻辑如何添加新项

    更新: 由@maks创建

    导入“包装:颤振/材料.省道”;
    void main()=>runApp(MyApp());
    类MyApp扩展了无状态小部件{
    @凌驾
    小部件构建(构建上下文){
    返回材料PP(
    家:脚手架(
    正文:中(
    子:容器(
    对齐:对齐.center,
    子项:itemscene(),
    装饰:盒子装饰(
    边界:边界(
    颜色:Colors.blueAccent,
    ),
    ),
    ),
    ),
    ),
    );
    }
    }
    类itemscene扩展StatefulWidget{
    @凌驾
    _ItemsCensState createState()=>\u ItemsCensState();
    }
    类_itemscenstate扩展状态{
    列表项=[
    ItemModel(偏移量:偏移量(70100),文本:“text1”),
    ItemModel(偏移量:偏移量(200100),文本:“text2”),
    ItemModel(偏移量:偏移量(200230),文本:“text3”),
    ];
    函数onDragStart(int索引)=>(x,y){
    设置状态(){
    items[索引]=items[索引].copyWithNewOffset(偏移量(x,y));
    });
    };
    @凌驾
    小部件构建(构建上下文){
    返回堆栈(
    儿童:[
    定制油漆(
    大小:大小(双无限,双无限),
    画家:曲线画家(
    偏移量:items.map((item)=>item.offset.toList(),
    ),
    ),
    …_buildItems()
    ],
    );
    }
    列表_buildItems(){
    最终res=[];
    items.asMap().forEach((ind,item){
    res.add(_项(
    onDragStart:onDragStart(ind),
    抵销:item.offset,
    text:item.text,
    ));
    });
    返回res;
    }
    }
    类_项扩展了无状态小部件{
    _项目({
    关键点,
    这个,抵消,,
    这是一个开始,
    这个文本,
    });
    最终双尺寸=100;
    最终偏移量;
    最终功能启动;
    最终字符串文本;
    _手抹布(细节){
    印刷(详情);
    var x=details.globalPosition.dx;
    变量y=details.globalPosition.dy;
    onDragStart(x,y);
    }
    @凌驾
    小部件构建(构建上下文){
    返回定位(
    左:offset.dx-大小/2,
    顶部:offset.dy-大小/2,
    儿童:手势检测器(
    onPanStart:_handleDrag,
    onPanUpdate:_handleDrag,
    子:容器(
    宽度:大小,
    高度:尺寸,
    子:文本(Text),
    装饰:盒子装饰(
    颜色:颜色,白色,
    边界:边界(
    颜色:Colors.blueAccent,
    ),
    ),
    ),
    ),
    );
    }
    }
    类CurvedPainter扩展了CustomPainter{
    CurvedPainter({this.offset});
    最终列表偏移量;
    @凌驾
    空心油漆(帆布,尺寸){
    如果(偏移量.长度>1){
    offset.asMap().forEach((索引,偏移量){
    如果(指数=0)返回;
    帆布拉丝(
    偏移量[索引-1],
    偏移量[索引],
    油漆()
    …颜色=颜色。红色
    ..冲程宽度=2,
    );
    });
    }
    }
    @凌驾
    布尔应该重新绘制(CurvedPainter oldDelegate)=>真;
    }
    类项模型{
    ItemModel({this.offset,this.text});
    最终偏移量;
    最终字符串文本;
    ItemModel copyWithNewOffset(偏移量偏移量){
    返回ItemModel(偏移量:偏移量,文本:文本);
    }
    }
    
    节点间关系的逻辑是什么?您是否可以发布您使用自定义painter类编写的代码,正如您已经尝试过的那样。。!还有节点在图中放置方式背后的逻辑。否则,这个问题似乎过于宽泛,看起来像是要求一个共同工作的实现。以我的拙见,在flutter中使用自定义画师绝对可以实现这一点。感谢您提供的优秀示例代码!我用它做了一个密码笔,以防有人想要一个交互式版本来试用:@Kherel-woooooo,太棒了,谢谢我的兄弟
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: Container(
                alignment: Alignment.center,
                child: ItemsScene(),
                decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.blueAccent,
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class ItemsScene extends StatefulWidget {
      @override
      _ItemsSceneState createState() => _ItemsSceneState();
    }
    
    class _ItemsSceneState extends State<ItemsScene> {
      List<ItemModel> items = [
        ItemModel(offset: Offset(70, 100), text: 'text1'),
        ItemModel(offset: Offset(200, 100), text: 'text2'),
        ItemModel(offset: Offset(200, 230), text: 'text3'),
      ];
    
      Function onDragStart(int index) => (x, y) {
            setState(() {
              items[index] = items[index].copyWithNewOffset(Offset(x, y));
            });
          };
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            CustomPaint(
              size: Size(double.infinity, double.infinity),
              painter: CurvedPainter(
                offsets: items.map((item) => item.offset).toList(),
              ),
            ),
            ..._buildItems()
          ],
        );
      }
    
      List<Widget> _buildItems() {
        final res = <Widget>[];
        items.asMap().forEach((ind, item) {
          res.add(_Item(
            onDragStart: onDragStart(ind),
            offset: item.offset,
            text: item.text,
          ));
        });
    
        return res;
      }
    }
    
    class _Item extends StatelessWidget {
      _Item({
        Key key,
        this.offset,
        this.onDragStart,
        this.text,
      });
    
      final double size = 100;
      final Offset offset;
      final Function onDragStart;
      final String text;
    
      _handleDrag(details) {
        print(details);
        var x = details.globalPosition.dx;
        var y = details.globalPosition.dy;
        onDragStart(x, y);
      }
    
      @override
      Widget build(BuildContext context) {
        return Positioned(
          left: offset.dx - size / 2,
          top: offset.dy - size / 2,
          child: GestureDetector(
            onPanStart: _handleDrag,
            onPanUpdate: _handleDrag,
            child: Container(
              width: size,
              height: size,
              child: Text(text),
              decoration: BoxDecoration(
                color: Colors.white,
                border: Border.all(
                  color: Colors.blueAccent,
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class CurvedPainter extends CustomPainter {
      CurvedPainter({this.offsets});
    
      final List<Offset> offsets;
    
      @override
      void paint(Canvas canvas, Size size) {
        if (offsets.length > 1) {
          offsets.asMap().forEach((index, offset) {
            if (index == 0) return;
            canvas.drawLine(
              offsets[index - 1],
              offsets[index],
              Paint()
                ..color = Colors.red
                ..strokeWidth = 2,
            );
          });
        }
      }
    
      @override
      bool shouldRepaint(CurvedPainter oldDelegate) => true;
    }
    
    class ItemModel {
      ItemModel({this.offset, this.text});
    
      final Offset offset;
      final String text;
    
      ItemModel copyWithNewOffset(Offset offset) {
        return ItemModel(offset: offset, text: text);
      }
    }