Flutter 如何根据用户的拖动实现两个组件之间绘制一条线,当多次拖动后如何保存所有的连线?

新手上路,请多包涵

我正在做一个让二部图相关的项目,用户可以拖动图内的任一节点到另外的节点上创建一条边。
现在的做法是让 CustomPaint 的 child 中包含 Draggable 列和 DragTarge 列,然后希望在拖动完成后绘制一条两个组件之间的连线。

目前的问题是,我现在可以实现拖动后绘制一条线,但是在拖动完成后显示出来,只有在热重载之后才会将线条绘制出来。然后当我拖动到另外一个组件时,之前已经存在的连线不能够保存下来。

请问我应该如何实现这两个功能呢?

画布实现

// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:get_demo/pages/canvas/painter/painter3.dart';

class Canvas3 extends StatefulWidget {
  const Canvas3({Key? key}) : super(key: key);

  @override
  _Canvas3State createState() => _Canvas3State();
}

class _Canvas3State extends State<Canvas3> {
  var start_offset = ValueNotifier(Offset.zero);
  var end_offset = ValueNotifier(Offset.zero);

  var key1 = GlobalKey();
  var key2 = GlobalKey();
  var key3 = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        width: 500,
        height: 500,
        color: Colors.red.shade100,
        // 创建画布
        child: CustomPaint(
          child: Column(
            children: [
              _buildItems(),
            ],
          ),
          foregroundPainter: Painter3(start_offset, end_offset),
        ),
      ),
    );
  }

  // 创建内容
  Row _buildItems() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        // Draggable 是左侧列
        Draggable(
          child: Container(
            key: key1,
            width: 100,
            height: 100,
            child: Image.asset('assets/images/Pig.png'),
            decoration: BoxDecoration(color: Colors.pink),
          ),
          data: 1,
          // 拖动反馈
          feedback: Container(
            width: 100,
            height: 100,
            decoration: BoxDecoration(color: Colors.lightGreen),
          ),
          // 当开始拖动时计算当前盒子的坐标中心点
          onDragStarted: () {
            var box = key1.currentContext!.findRenderObject() as RenderBox;
            var x = box.localToGlobal(Offset.zero).dx + box.size.width / 2;
            var y = box.localToGlobal(Offset.zero).dy - box.size.height / 2;
            start_offset.value = Offset(x, y);
          },
        ),

        // DragTarget 是右侧列
        Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            DragTarget(
              builder: (context, c, r) => Container(
                key: key2,
                width: 100,
                height: 100,
                child: Image.asset('assets/images/Chicken.png'),
                decoration: BoxDecoration(color: Colors.green),
              ),
              // 接收到数据获取当前盒子的坐标中心点
              onAccept: (_) {
                var box = key2.currentContext!.findRenderObject() as RenderBox;
                var x = box.localToGlobal(Offset.zero).dx + box.size.width / 2;
                var y = box.localToGlobal(Offset.zero).dy - box.size.height / 2;
                end_offset.value = Offset(x, y);
              },
            ),
            SizedBox(
              height: 50,
            ),
            DragTarget(
              builder: (context, c, r) => Container(
                key: key3,
                width: 100,
                height: 100,
                child: Image.asset('assets/images/Horse.png'),
                decoration: BoxDecoration(color: Colors.green),
              ),
              onAccept: (_) {
                var box = key3.currentContext!.findRenderObject() as RenderBox;
                var x = box.localToGlobal(Offset.zero).dx + box.size.width / 2;
                var y = box.localToGlobal(Offset.zero).dy - box.size.height / 2;
                end_offset.value = Offset(x, y);
              },
            ),
          ],
        )
      ],
    );
  }
}

画笔绘制实现

// ignore_for_file: prefer_final_fields, prefer_typing_uninitialized_variables

import 'package:flutter/material.dart';

class Painter3 extends CustomPainter {
  // 绘制起点
  var _start;
  // 绘制终点
  var _end;

  Painter3(this._start, this._end);

  @override
  void paint(Canvas canvas, Size size) {
    // 画笔属性
    var _paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round;

    // 绘制线条
    canvas.clipRect(Offset.zero & size);
    canvas.drawLine(_start.value, _end.value, _paint);
  }

  @override
  bool shouldRepaint(Painter3 oldDelegate) {
    return true;
  }
}
当前实现的结果

image.png

阅读 3.7k
1 个回答
新手上路,请多包涵

经过一番折腾自己解决了。
解决过程如下:

  1. 在上述获取到 widget 的中心点之后,之所以不刷新是因为页面状态的刷新需要 setState()一次。所以在 onAccept 这里调用 setState 来控制页面刷新。具体做法就是在这个回调函数里计算当前 DragTarget 的中心点。
  2. 根据1 能够完成在拖拽之后的绘制,要想保留多个线段,实际就是要在画笔类中保存多个起点终点对,用 list 即可。每次拖动完成时,添加一个值到画笔保存的 List 中。在画笔的 shouldRepaint 函数中,直接对比前一帧的 List 和当前 List 是否一致,不一致则重绘。

希望这个问题以及解决思路对后来的同学有所帮助。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题