浏览代码

1、新增白板业务组件

bakamaka.guan 2 年之前
父节点
当前提交
2b4396a687

+ 75 - 0
lib/components/white_board/color_util.dart

@@ -0,0 +1,75 @@
+import 'dart:convert';
+import 'dart:math';
+import 'package:flutter/material.dart';
+import 'package:crypto/crypto.dart';
+
+class ColorUtil {
+  static Color generateColor(String uuid) {
+    final hash = sha256.convert(utf8.encode(uuid)).toString();
+    final intHash = int.parse(hash.substring(hash.length - 8), radix: 16);
+    final Random random = Random(intHash);
+    final double r = random.nextInt(256).toDouble();
+    final double g = random.nextInt(256).toDouble();
+    final double b = random.nextInt(256).toDouble();
+
+    List<double> hsl = rgbToHsl(r, g, b);
+    List<int> rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
+    return Color.fromRGBO(
+      rgb[0],
+      rgb[1],
+      rgb[2],
+      1,
+    );
+  }
+
+  static List<double> rgbToHsl(double r, double g, double b) {
+    r /= 255;
+    g /= 255;
+    b /= 255;
+
+    double max1 = max(max(r, g), b);
+    double min1 = min(min(r, g), b);
+    double h = 0;
+
+    if (max1 == min1) {
+      h = 0; // achromatic
+    } else {
+      double d = max1 - min1;
+      if (max1 == r) {
+        h = (g - b) / d + (g < b ? 6 : 0);
+      } else if (max1 == g) {
+        h = (b - r) / d + 2;
+      } else if (max1 == b) {
+        h = (r - g) / d + 4;
+      }
+      h /= 6;
+    }
+
+    return [h, 0.8, 0.5];
+  }
+
+  static List<int> hslToRgb(double h, double s, double l) {
+    double r, g, b;
+
+    if (s == 0) {
+      r = g = b = l; // achromatic
+    } else {
+      double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+      double p = 2 * l - q;
+      r = hue2rgb(p, q, h + 1 / 3);
+      g = hue2rgb(p, q, h);
+      b = hue2rgb(p, q, h - 1 / 3);
+    }
+
+    return [r * 255, g * 255, b * 255].map((value) => value.round()).toList();
+  }
+
+  static double hue2rgb(double p, double q, double t) {
+    if (t < 0) t += 1;
+    if (t > 1) t -= 1;
+    if (t < 1 / 6) return p + (q - p) * 6 * t;
+    if (t < 1 / 2) return q;
+    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+    return p;
+  }
+}

+ 5 - 0
lib/components/white_board/index.dart

@@ -0,0 +1,5 @@
+library white_board;
+
+export './structure.dart';
+export './white_board.dart';
+export './color_util.dart';

+ 107 - 0
lib/components/white_board/structure.dart

@@ -0,0 +1,107 @@
+import 'dart:math';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+
+/// 数据类型定义
+
+/// 绘制类型:直线、曲线
+enum PaintType { straightLine, curvedLine }
+
+/// 绘制状态:正在绘制、已完成、隐藏
+enum PaintState { doing, done, hide }
+
+/// Point 点类
+class Point {
+  final double x;
+  final double y;
+
+  const Point({
+    required this.x,
+    required this.y,
+  });
+
+  factory Point.fromOffset(Offset offset, double width, double height) {
+    return Point(
+      x: double.parse((offset.dx / width).toStringAsFixed(4)),
+      y: double.parse((offset.dy / height).toStringAsFixed(4)),
+    );
+  }
+
+  factory Point.fromJson(
+      Map<String, dynamic> json, double width, double height) {
+    return Point(
+      x: json['x'],
+      y: json['y'],
+    );
+  }
+
+  factory Point.fromList(List<dynamic> offSetList) {
+    return Point(
+      x: offSetList[0],
+      y: offSetList[1],
+    );
+  }
+
+  List<double> toList() {
+    final List<double> offestList = [];
+    offestList.addAll([x, y]);
+    return offestList;
+  }
+
+  Map<String, dynamic> toJson(width, height) {
+    final json = Map<String, dynamic>();
+    json['x'] = x;
+    json['y'] = y;
+    return json;
+  }
+
+  double get distance => sqrt(x * x + y * y);
+
+  // Point operator -(Point other) => Point(x: x - other.x, y: y - other.y);
+
+  Offset toOffset(double width, double height) => Offset(x * width, y * height);
+}
+
+/// Line 线类
+class Line {
+  List<Point> points = [];
+  PaintState state;
+  PaintType paintType;
+  double strokeWidth;
+  Color color;
+  String userId;
+
+  Line({
+    this.color = Colors.black,
+    this.strokeWidth = 1,
+    this.state = PaintState.doing,
+    this.paintType = PaintType.curvedLine,
+    required this.userId,
+  });
+
+  void paint(Canvas canvas, Paint paint, double width, double height) {
+    paint
+      ..color = color
+      ..isAntiAlias = true
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = strokeWidth
+      ..strokeCap = StrokeCap.round;
+    List<Point> pointList = [];
+    if (points.length < 10) {
+      pointList = points;
+    } else {
+      for (int i = 0; i < points.length; i++) {
+        if (i % 3 == 0) {
+          pointList.add(points[i]);
+        }
+      }
+    }
+
+    canvas.drawPoints(
+      PointMode.polygon,
+      pointList.map<Offset>((e) => e.toOffset(width, height)).toList(),
+      paint,
+    );
+  }
+}

+ 156 - 0
lib/components/white_board/white_board.dart

@@ -0,0 +1,156 @@
+import 'dart:convert';
+
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_lib_business_components/components/white_board/color_util.dart';
+import 'package:fis_lib_business_components/components/white_board/structure.dart';
+import 'package:fis_lib_business_components/components/white_board/white_board_painter.dart';
+import 'package:flutter/material.dart';
+
+class WhiteBoard extends StatefulWidget {
+  const WhiteBoard({
+    key,
+    required this.userId,
+    required this.paintType,
+    required this.sendData,
+    required this.onReceiveData,
+    required this.onClearCanavs,
+  });
+
+  /// 用户id
+  final String userId;
+
+  /// 当前绘制模式
+  final PaintType paintType;
+
+  /// 发送数据(接 JsonRPC)
+  final void Function(String data) sendData;
+
+  /// 接收数据(接 WebSocket)
+  final FEventHandler<String> onReceiveData;
+
+  /// 画布清除事件
+  final FEventHandler<String> onClearCanavs;
+
+  @override
+  State<WhiteBoard> createState() => _WhiteBoardState();
+}
+
+class _WhiteBoardState extends State<WhiteBoard> {
+  /// 画布尺寸
+  late double canvasWidth = 0.0;
+  late double canvasHeight = 0.0;
+
+  /// 当前用户的画笔
+  WhiteBoardPen myPen = WhiteBoardPen();
+
+  /// 当前用户的画笔颜色
+  Color get myPenColor => ColorUtil.generateColor(widget.userId);
+
+  /// 固定的画笔粗细
+  static const double strokeWidth = 3.0;
+
+  @override
+  void initState() {
+    super.initState();
+    widget.onReceiveData.addListener((sender, data) {
+      _onReceiveData(data);
+    });
+    widget.onClearCanavs.addListener((sender, userId) {
+      _clearUserLines(userId);
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    canvasWidth = MediaQuery.of(context).size.width;
+    canvasHeight = MediaQuery.of(context).size.height;
+    return MouseRegion(
+      cursor: SystemMouseCursors.click,
+      child: GestureDetector(
+        onPanDown: _onPanDown,
+        onPanUpdate: _onPanUpdate,
+        onPanEnd: _onPanEnd,
+        onPanCancel: _onPanCancel,
+        onDoubleTap: myPen.clear,
+        child: CustomPaint(
+          size: MediaQuery.of(context).size,
+          painter: WhiteBoardPainter(
+            pen: myPen,
+            width: canvasWidth,
+            height: canvasHeight,
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 按下时,生成新的线实体
+  void _onPanDown(DragDownDetails details) {
+    Line line = Line(
+      color: myPenColor,
+      strokeWidth: strokeWidth,
+      paintType: widget.paintType,
+      userId: widget.userId,
+    );
+    myPen.pushLine(line);
+  }
+
+  /// 拖动时,生成新的点实体,加入到当前线实体中
+  void _onPanUpdate(DragUpdateDetails details) {
+    myPen.pushPoint(Point.fromOffset(
+      details.localPosition,
+      canvasWidth,
+      canvasHeight,
+    ));
+  }
+
+  /// 抬起时,发送数据
+  void _onPanEnd(DragEndDetails details) {
+    _sentData();
+    myPen.doneLine();
+  }
+
+  /// 发送数据
+  void _sentData() {
+    List<List<double>> pathIdLists = [];
+    for (int i = 0; i < myPen.activeLine.points.length; i++) {
+      pathIdLists.add(myPen.activeLine.points[i].toList());
+    }
+    widget.sendData(jsonEncode({
+      "action": "add",
+      "type": widget.paintType.index,
+      "userId": widget.userId,
+      "lineId": "",
+      "pathIdList": jsonEncode(pathIdLists),
+    }));
+  }
+
+  /// 接收数据
+  void _onReceiveData(String crossData) async {
+    var data = jsonDecode(crossData);
+    List<dynamic> pathIdList = jsonDecode(data["pathIdList"]);
+    Line line = Line(
+      color: ColorUtil.generateColor(data["userId"]),
+      strokeWidth: strokeWidth,
+      userId: data["userId"],
+    );
+
+    myPen.pushLine(line);
+
+    for (int i = 0; i < pathIdList.length; i++) {
+      myPen.pushPoint(Point.fromList(
+        pathIdList[i],
+      ));
+      await Future.delayed(const Duration(milliseconds: 2));
+    }
+    myPen.doneLine();
+  }
+
+  void _clearUserLines(String userId) {
+    myPen.clearCurrectUserLines(userId);
+  }
+
+  void _onPanCancel() {
+    myPen.removeEmpty();
+  }
+}

+ 85 - 0
lib/components/white_board/white_board_painter.dart

@@ -0,0 +1,85 @@
+import 'package:fis_lib_business_components/components/white_board/structure.dart';
+import 'package:flutter/material.dart';
+
+/// 白板 Canvas
+class WhiteBoardPainter extends CustomPainter {
+  final WhiteBoardPen pen;
+  final double width;
+  final double height;
+
+  WhiteBoardPainter({required this.pen, this.width = 0.0, this.height = 0.0})
+      : super(repaint: pen);
+
+  final Paint _paint = Paint();
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    /// 遍历每一根线,绘制
+    for (var line in pen.lines) {
+      line.paint(
+        canvas,
+        _paint,
+        width,
+        height,
+      );
+    }
+  }
+
+  @override
+  bool shouldRepaint(covariant WhiteBoardPainter oldDelegate) {
+    bool needRepaint = oldDelegate.pen != pen;
+    return needRepaint;
+  }
+}
+
+/// 白板画笔
+class WhiteBoardPen extends ChangeNotifier {
+  final List<Line> _lines = [];
+
+  List<Line> get lines => _lines;
+
+  Line get activeLine => _lines.singleWhere(
+        (element) => element.state == PaintState.doing,
+        orElse: () => Line(userId: ''),
+      );
+
+  void pushLine(Line line) {
+    _lines.add(line);
+  }
+
+  void pushPoint(Point point) {
+    activeLine.points.add(point);
+    if (activeLine.paintType == PaintType.straightLine) {
+      activeLine.points = [activeLine.points.first, activeLine.points.last];
+    }
+    notifyListeners();
+  }
+
+  void doneLine() {
+    activeLine.state = PaintState.done;
+    notifyListeners();
+  }
+
+  void clear() {
+    for (var line in _lines) {
+      line.points.clear();
+    }
+    _lines.clear();
+    notifyListeners();
+  }
+
+  void clearCurrectUserLines(String userId) {
+    for (int i = 0; i < _lines.length; i++) {
+      if (_lines[i].userId == userId) {
+        _lines[i].points.clear();
+      }
+    }
+    _lines.removeWhere((element) => element.userId == userId);
+
+    notifyListeners();
+  }
+
+  void removeEmpty() {
+    _lines.removeWhere((element) => element.points.isEmpty);
+  }
+}

+ 1 - 0
lib/index.dart

@@ -8,3 +8,4 @@ export 'components/from_configure_components/configure_layout.dart';
 export 'components/from_configure_components/configure_input.dart';
 export 'components/from_configure_components/configure_select.dart';
 export 'components/from_configure_components/configure_text_area.dart';
+export 'components/white_board/index.dart';