Flutter图片添加水印,矩形或文字标注,自定义涂鸦到底应该怎么做?

前段时间参照今日相机有个需求,具体需求如下:
1.拍照或者相册选择图片在编辑时候可以添加一个自定义的水印(包含时间和定位信息);
2.能在图片上面绘制矩形或者椭圆;
3.能在图片上面编辑文字标注,文字标注区域可以拖动;
4.可以自定义涂鸦;
Flutter也有很多库,但全网好像并没有此类的库,那就自己动手实现。因为公司项目,我并没有整理Demo出来。接下来我主要把自己的一些思路整理出来,也会放一些片段式的代码。

可能会遇到的问题:

  • 拍照图片和相册图片编辑区域适配问题?
    *绘制区域到底由什么决定?
    *整个过程会经历网络图片到本地,再从本地编辑之后上传,上传后失真问题
    *图片修改上传后与自己标注不成比例问题
    *文字标注拖动及边界界定问题
    *多个图层同时进行操作可能会遇到的问题

拍照入口

需要引入的包

image_picker: ^0.8.0+1  #拍照

拍照入口代码

  final picker = ImagePicker();
                    var image =
                        await picker.getImage(source: ImageSource.camera);
                    if (image != null) {
                      final bytes = await image.readAsBytes();
                      UI.decodeImageFromList(bytes, (image) {
                        NavigatorUtil.push(
                            mContext,
                            ImageEditPage(
                              uint8list: bytes,
                              width: image.width,
                              height: image.height,
                              projectName:
                                  widget.pageModelContents.project.name,
                              typeEventBus: typeEventBus,
                              picInfomationModel: PicInfomationModel(1,
                                  pageModelContents: pageModelContents),
                            ));
                      });
                    }

简单描述下上面这段代码逻辑,调用手机相机拍照,获取到图片转Uint8List,并根据Uint8List获取图片的真实宽高,然后跳转了ImageEditPage,ImageEditPage中uint8list,width,height,是三个重要参数,后面需要用到,至于其他参数也是需求中逻辑需要。

相册图片上传后编辑入口

Image image = Image.network(picModel.url);
                      image.image
                          .resolve(new ImageConfiguration())
                          .addListener(new ImageStreamListener(
                        (ImageInfo info, bool _) async {
                          Uint8List uint8List =
                              await NetWorkImageUtil.netWorkUint8ListImage(
                                  picModel.url);
                          if (uint8List != null) {
                            NavigatorUtil.push(
                                mContext,
                                ImageEditPage(
                                  uint8list: uint8List,
                                  width: info.image.width,
                                  height: info.image.height,
                                  projectName:
                                      widget.pageModelContents.project.name,
                                  typeEventBus: typeEventBus,
                                  picInfomationModel: PicInfomationModel(3,
                                      pageModelContents: pageModelContents,
                                      picModel: picModel),
                                ));
                          }
                        },
                      ));

因为相册选择照片是多张的,比不太适合去添加水印,所以是添加完成之后可以编辑的,接下来我们来看ImageEditPage代码;

ImageEditPage

import 'dart:typed_data';
import 'dart:ui' as UI;

import 'package:event_bus/event_bus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:yirui_flutter_app/abstracs/abstract_class.dart';
import 'package:yirui_flutter_app/dialog/handwrite/canvasremark_dialog.dart';
import 'package:yirui_flutter_app/dialog/handwrite/construction_dialog.dart';
import 'package:yirui_flutter_app/dialog/handwrite/handtext_dialog.dart';
import 'package:yirui_flutter_app/dialog/handwrite/handtuya_dialog.dart';
import 'package:yirui_flutter_app/dialog/handwrite/watermark_dialog.dart';
import 'package:yirui_flutter_app/event/handwrite_event.dart';
import 'package:yirui_flutter_app/event/picwaterupload_event.dart';
import 'package:yirui_flutter_app/model/picinfo_model.dart';
import 'package:yirui_flutter_app/util/adapt_util.dart';
import 'package:yirui_flutter_app/util/assetsload_util.dart';
import 'package:yirui_flutter_app/util/color_util.dart';
import 'package:yirui_flutter_app/util/handline_util.dart';
import 'package:yirui_flutter_app/util/handlinelast_util.dart';
import 'package:yirui_flutter_app/util/handractLast_util.dart';
import 'package:yirui_flutter_app/util/handract_util.dart';
import 'package:yirui_flutter_app/util/location_util.dart';
import 'package:yirui_flutter_app/util/screen_utils.dart';
import 'package:yirui_flutter_app/view/draggable_edit.dart';
import 'package:yirui_flutter_app/view/draggablelast_edit.dart';

class ImageEditPage extends StatefulWidget {
  final Uint8List uint8list;
  final int height;
  final int width;
  final String projectName;
  final PicInfomationModel picInfomationModel;
  final EventBus typeEventBus;

  ImageEditPage({
    this.uint8list,
    this.height,
    this.width,
    this.projectName,
    this.picInfomationModel,
    this.typeEventBus,
  });

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

class _ImageEditPageState extends State<ImageEditPage> with OnLocationListener {
  ///可绘制区域背景真实高度
  double canvasbg_height = 0;

  ///可绘制区域背景真实宽度
  double canvasbg_width = 0;

  ///图片真实绘制高度
  double pics_height = 0;

  ///图片真实绘制宽度
  double pics_width = 0;

  ///地理位置
  String address = null;

  ///默认选中水印
  bool _selectDeflutWater = true;

  ///默认无文字标注
  bool _selectDefaulttext = false;

  ///默认无图形标注
  bool _defaultHandRect = false;

  ///默认无自定义涂鸦
  bool selectDefaultTuYa = false;

  EventBus eventBus;
  EventBus eventBusBiaoZhu;
  EventBus eventBusLine;

  ///默认文字标注文案
  String hittext = "暂无";

  ///默认矩形椭圆无  默认不进行任何图形绘制
  int selectReactType = 3;

  ///水印施工区域
  String construction = "点我修改";

  HandReactBoardController cosntrollerReact = HandReactBoardController();
  HandLineBoardController cosntrollerLine = HandLineBoardController();

  HandReactLastBoardController cosntrollerLastReact =
      HandReactLastBoardController();
  HandLineBoardLastController cosntrollerLastLine =
      HandLineBoardLastController();

  ScrollController thirdColumnController = ScrollController();
  ScrollController secondedRowController = ScrollController();

  GlobalKey _handglobalKey = new GlobalKey();

  Offset draggLastoffset = Offset(0, 10);

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    LocationUtil().onLocation(this, "");
    if (eventBusBiaoZhu == null) {
      eventBusBiaoZhu = new EventBus();
    }
    if (eventBusLine == null) {
      eventBusLine = new EventBus();
    }

    if (eventBus == null) {
      eventBus = new EventBus();
      eventBus.on<HandWriteMarkEvent>().listen((event) {
        if (event.obj["type"] == 1) {
          if (mounted) {
            setState(() {
              _selectDeflutWater = event.obj["show"];
            });
          }
        } else if (event.obj["type"] == 2) {
          if (mounted) {
            setState(() {
              _selectDefaulttext = event.obj["show"];
            });
          }
        } else if (event.obj["type"] == 3) {
          if (mounted) {
            setState(() {
              hittext = event.obj["hittext"];
            });
          }
        } else if (event.obj["type"] == 4) {
          if (mounted) {
            setState(() {
              selectReactType = event.obj["tag"];
              if (selectReactType == 1 || selectReactType == 2) {
                _defaultHandRect = true;
              } else {
                _defaultHandRect = false;
              }

              ///选择绘制矩形或者椭圆  则涂鸦层要影藏
              if (_defaultHandRect) {
                selectDefaultTuYa = false;
                cosntrollerLine.clearBoard();
                cosntrollerLastLine.clearBoard();
              }
            });

            cosntrollerReact.clearBoard();
            cosntrollerLastReact.clearBoard();
          }
        } else if (event.obj["type"] == 5) {
          if (mounted) {
            setState(() {
              selectDefaultTuYa = event.obj["show"];

              ///选择涂鸦  则绘制椭圆和矩形要影藏
              if (selectDefaultTuYa) {
                _defaultHandRect = false;
                selectReactType = 3;
                cosntrollerReact.clearBoard();
                cosntrollerLastReact.clearBoard();
              }
            });
            cosntrollerLine.clearBoard();
            cosntrollerLastLine.clearBoard();
          }
        } else if (event.obj["type"] == 6) {
          setState(() {
            this.draggLastoffset = Offset(event.obj["x"], event.obj["y"]);
          });
        } else if (event.obj["type"] == 7) {
          setState(() {
            construction = event.obj["hittext"];
          });
        }
      });
    }
  }

  @override
  void dispose() {
    super.dispose();
    if (eventBus != null) {
      eventBus.destroy();
      eventBus = null;
    }
    if (eventBusBiaoZhu != null) {
      eventBusBiaoZhu.destroy();
      eventBusBiaoZhu = null;
    }
    if (eventBusLine != null) {
      eventBusLine.destroy();
      eventBusLine = null;
    }
    cosntrollerReact.dispose();
    cosntrollerLine.dispose();

    cosntrollerLastReact.dispose();
    cosntrollerLastLine.dispose();

    thirdColumnController.dispose();
    secondedRowController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    ///计算图片距离顶部高度
    double margin_tu_height = 0;

    ///计算图片距离左侧距离
    double margin_tu_width = 0;

    ScreenUtils screenUtils = ScreenUtils.getInstance();
    canvasbg_height = screenUtils.screenHeight -
        screenUtils.statusBarHeight -
        Adapt.px(140) * 2;
    canvasbg_width = screenUtils.screenWidth;

    double cs_height = (screenUtils.screenWidth * widget.height) / widget.width;
    if (cs_height >= canvasbg_height) {
      ///图片宽度大于等于背景高度,去缩放图片宽度
      pics_height = canvasbg_height;
      pics_width = (pics_height * widget.width) / widget.height;

      margin_tu_height = screenUtils.statusBarHeight + Adapt.px(140);
      margin_tu_width = (screenUtils.screenWidth - pics_width) / 2;
    } else {
      ///图片宽度缩放到屏幕宽度,高度根据屏幕宽度等比缩放
      pics_height = (screenUtils.screenWidth * widget.height) / widget.width;
      pics_width = screenUtils.screenWidth;

      margin_tu_height = (canvasbg_height - pics_height) / 2 +
          screenUtils.statusBarHeight +
          Adapt.px(140);
      margin_tu_width = 0;
    }

    ///水印宽高系数
    double xi_w = ((Adapt.px(388) * widget.width) / pics_width) / Adapt.px(388);
    double xi_h =
        ((Adapt.px(254) * widget.height) / pics_height) / Adapt.px(254);

    return Scaffold(
        resizeToAvoidBottomInset: false,
        backgroundColor: Colors.black,
        body: Stack(
          children: [
            ListView(
              controller: thirdColumnController,
              children: [
                SingleChildScrollView(
                    controller: secondedRowController,
                    scrollDirection: Axis.horizontal, //horizontal
                    child: Stack(
                      children: [
                        RepaintBoundary(
                          key: _handglobalKey,
                          child: Container(
                            height: double.parse(widget.height.toString()),
                            width: double.parse(widget.width.toString()),
                            child: Stack(
                              children: [
                                Container(
                                  height:
                                      double.parse(widget.height.toString()),
                                  width: double.parse(widget.width.toString()),
                                  child: Image.memory(widget.uint8list,
                                      fit: BoxFit.cover,
                                      filterQuality: FilterQuality.high),
                                ),
                                Offstage(
                                    offstage: !_selectDeflutWater,
                                    child: Container(
                                      height: double.parse(
                                          widget.height.toString()),
                                      width:
                                          double.parse(widget.width.toString()),
                                      alignment: Alignment.bottomLeft,
                                      child: Container(
                                        width: Adapt.px(388) * xi_w,
                                        height: Adapt.px(254) * xi_h,
                                        margin: EdgeInsets.only(
                                            left: Adapt.px(10) * xi_w,
                                            bottom: Adapt.px(10) * xi_h),
                                        child: Stack(
                                          children: <Widget>[
                                            ClipRRect(
                                              borderRadius:
                                                  BorderRadius.circular(
                                                      Adapt.px(15) * xi_w),
                                              child: Container(
                                                child: Column(
                                                  children: [
                                                    Opacity(
                                                      opacity: 0.8,
                                                      child: Container(
                                                        height:
                                                            Adapt.px(54) * xi_h,
                                                        color:
                                                            ColorUtil.colorblue,
                                                      ),
                                                    ),
                                                    Opacity(
                                                      opacity: 0.7,
                                                      child: Container(
                                                        height: Adapt.px(200) *
                                                            xi_h,
                                                        color: ColorUtil
                                                            .color2c2c2c,
                                                      ),
                                                    )
                                                  ],
                                                ),
                                              ),
                                            ),
                                            Container(
                                              width: double.infinity,
                                              child: Column(
                                                children: <Widget>[
                                                  Container(
                                                    width: Adapt.px(388) * xi_w,
                                                    margin: EdgeInsets.only(
                                                        top:
                                                            Adapt.px(8) * xi_h),
                                                    child: Row(
                                                      children: [
                                                        Container(
                                                          margin: EdgeInsets.only(
                                                              left:
                                                                  Adapt.px(10) *
                                                                      xi_w,
                                                              right:
                                                                  Adapt.px(10) *
                                                                      xi_w),
                                                          child: Image.asset(
                                                            "images/icon_yuan.png",
                                                            width:
                                                                Adapt.px(20) *
                                                                    xi_w,
                                                            height:
                                                                Adapt.px(20) *
                                                                    xi_h,
                                                            excludeFromSemantics:
                                                                true,
                                                            gaplessPlayback:
                                                                true,
                                                          ),
                                                        ),
                                                        Flexible(
                                                          child: Text(
                                                              widget.projectName ==
                                                                      null
                                                                  ? "暂无"
                                                                  : widget
                                                                      .projectName
                                                                      .toString(),
                                                              style: TextStyle(
                                                                  fontSize:
                                                                      Adapt.px(
                                                                              26) *
                                                                          xi_h,
                                                                  color: Colors
                                                                      .white,
                                                                  fontWeight:
                                                                      FontWeight
                                                                          .w600,
                                                                  decoration:
                                                                      TextDecoration
                                                                          .none),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                      ],
                                                    ),
                                                  ),
                                                  Container(
                                                    width: Adapt.px(388) * xi_w,
                                                    margin: EdgeInsets.only(
                                                        top: Adapt.px(10) *
                                                            xi_h),
                                                    child: Row(
                                                      children: [
                                                        Container(
                                                          margin: EdgeInsets.only(
                                                              left:
                                                                  Adapt.px(10) *
                                                                      xi_w,
                                                              right:
                                                                  Adapt.px(10) *
                                                                      xi_w),
                                                          child: Text("施工区域:",
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                        Flexible(
                                                          child: Text(
                                                              construction,
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                      ],
                                                    ),
                                                  ),
                                                  Container(
                                                    width: Adapt.px(388) * xi_w,
                                                    child: Row(
                                                      children: [
                                                        Container(
                                                          margin: EdgeInsets.only(
                                                              left:
                                                                  Adapt.px(10) *
                                                                      xi_w,
                                                              right:
                                                                  Adapt.px(10) *
                                                                      xi_w),
                                                          child: Text("拍摄时间:",
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                        Flexible(
                                                          child: Text(
                                                              new DateTime.now()
                                                                  .toString()
                                                                  .substring(
                                                                      0, 16),
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                      ],
                                                    ),
                                                  ),
                                                  Container(
                                                    width: Adapt.px(388) * xi_w,
                                                    child: Row(
                                                      crossAxisAlignment:
                                                          CrossAxisAlignment
                                                              .start,
                                                      children: [
                                                        Container(
                                                          alignment:
                                                              Alignment.topLeft,
                                                          margin: EdgeInsets.only(
                                                              left:
                                                                  Adapt.px(10) *
                                                                      xi_w,
                                                              right:
                                                                  Adapt.px(10) *
                                                                      xi_w),
                                                          child: Text("拍摄位置:",
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 1,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                        Flexible(
                                                          child: Text(
                                                              address == null
                                                                  ? "定位中..."
                                                                  : address,
                                                              style: TextStyle(
                                                                fontSize:
                                                                    Adapt.px(
                                                                            26) *
                                                                        xi_h,
                                                                color: Colors
                                                                    .white,
                                                              ),
                                                              maxLines: 3,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis),
                                                        ),
                                                      ],
                                                    ),
                                                  ),
                                                ],
                                              ),
                                            ),
                                          ],
                                        ),
                                      ),
                                    )),
                                Offstage(
                                    offstage: !_selectDefaulttext,
                                    child: Container(
                                        height: double.parse(
                                            widget.height.toString()),
                                        width: double.parse(
                                            widget.width.toString()),
                                        child: Stack(
                                          children: [
                                            DraggableLastWiget(
                                              widgetColor: Colors.transparent,
                                              margin_tu_width: margin_tu_width,
                                              margin_tu_height:
                                                  margin_tu_height,
                                              rc_width: Adapt.px(400),
                                              rc_height: Adapt.px(200),
                                              hittext: hittext == null
                                                  ? "暂无"
                                                  : hittext,
                                              xi_w: xi_w,
                                              xi_h: xi_h,
                                              draggLastoffset: draggLastoffset,
                                            )
                                          ],
                                        ))),
                                Offstage(
                                    offstage: !_defaultHandRect,
                                    child: Container(
                                      width: pics_width,
                                      height: pics_height,
                                      child: HandReactLastBoard(
                                        boardController: cosntrollerLastReact,
                                        paintWidth: 5,
                                        painColor: Colors.red,
                                        width: pics_width,
                                        height: pics_height,
                                        type: selectReactType,
                                        eventBusBiaoZhu: eventBusBiaoZhu,
                                        xi_w: xi_w,
                                        xi_h: xi_h,
                                      ),
                                    )),
                                Offstage(
                                    offstage: !selectDefaultTuYa,
                                    child: Container(
                                      width: pics_width,
                                      height: pics_height,
                                      child: HandLineLastBoard(
                                        boardController: cosntrollerLastLine,
                                        paintWidth: 5,
                                        painColor: Colors.red,
                                        width: pics_width,
                                        height: pics_height,
                                        eventBusLine: eventBusLine,
                                        xi_w: xi_w,
                                        xi_h: xi_h,
                                      ),
                                    ))
                              ],
                            ),
                          ),
                        ),

                        ///用于遮挡真实底层View
                        Container(
                          color: Colors.black,
                          height: double.parse(widget.height.toString()),
                          width: double.parse(widget.width.toString()),
                        )
                      ],
                    )),
              ],
            ),
            Container(
              child: Column(
                children: [
                  Container(
                    height: Adapt.px(140),
                    margin: EdgeInsets.only(
                      top: ScreenUtils.getInstance().statusBarHeight,
                    ),
                    padding: EdgeInsets.only(
                        left: Adapt.px(20), right: Adapt.px(20)),
                    color: ColorUtil.color141414,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        GestureDetector(
                          onTap: () {
                            Navigator.pop(context);
                          },
                          child: Text('取消',
                              style: TextStyle(
                                fontSize: Adapt.px(36),
                                color: Colors.white,
                              )),
                        ),
                        Container(
                          width: Adapt.px(130),
                          height: Adapt.px(70),
                          child: RaisedButton(
                            color: Colors.blue,
                            shape: RoundedRectangleBorder(
                              borderRadius: new BorderRadius.circular(18.0),
                            ),
                            child: Text(
                              '保存',
                              style: TextStyle(
                                  fontSize: Adapt.px(30), color: Colors.white),
                            ),
                            onPressed: () {
                              _savePicUrl();
                            },
                          ),
                        ),
                      ],
                    ),
                  ),
                  Flexible(
                      child: Container(
                    alignment: Alignment.center,
                    child: Stack(
                      children: [
                        Container(
                          width: pics_width,
                          height: pics_height,
                          child: Image.memory(widget.uint8list,
                              fit: BoxFit.fitWidth,
                              filterQuality: FilterQuality.high),
                        ),
                        Offstage(
                            offstage: !_selectDeflutWater,
                            child: GestureDetector(
                              onTap: () {
                                showDialog<Null>(
                                    context: context, //BuildContext对象
                                    builder: (BuildContext context) {
                                      return GestureDetector(
                                        onTap: () {},
                                        child: ConstructionDialog(
                                          eventBus: eventBus,
                                        ),
                                      );
                                    });
                              },
                              child: Container(
                                width: pics_width,
                                height: pics_height,
                                alignment: Alignment.bottomLeft,
                                child: Container(
                                  width: Adapt.px(388),
                                  height: Adapt.px(254),
                                  margin: EdgeInsets.only(
                                      left: Adapt.px(10), bottom: Adapt.px(10)),
                                  child: Stack(
                                    children: <Widget>[
                                      ClipRRect(
                                        borderRadius:
                                            BorderRadius.circular(Adapt.px(15)),
                                        child: Container(
                                          child: Column(
                                            children: [
                                              Opacity(
                                                opacity: 0.8,
                                                child: Container(
                                                  height: Adapt.px(54),
                                                  color: ColorUtil.colorblue,
                                                ),
                                              ),
                                              Opacity(
                                                opacity: 0.7,
                                                child: Container(
                                                  height: Adapt.px(200),
                                                  color: ColorUtil.color2c2c2c,
                                                ),
                                              )
                                            ],
                                          ),
                                        ),
                                      ),
                                      Container(
                                        width: double.infinity,
                                        child: Column(
                                          children: <Widget>[
                                            Container(
                                              width: Adapt.px(388),
                                              margin: EdgeInsets.only(
                                                  top: Adapt.px(8)),
                                              child: Row(
                                                children: [
                                                  Container(
                                                    margin: EdgeInsets.only(
                                                        left: Adapt.px(10),
                                                        right: Adapt.px(10)),
                                                    child: Image.asset(
                                                      "images/icon_yuan.png",
                                                      width: Adapt.px(20),
                                                      height: Adapt.px(20),
                                                      excludeFromSemantics:
                                                          true,
                                                      gaplessPlayback: true,
                                                    ),
                                                  ),
                                                  Flexible(
                                                    child: Text(
                                                        widget.projectName ==
                                                                null
                                                            ? "暂无"
                                                            : widget.projectName
                                                                .toString(),
                                                        style: TextStyle(
                                                            fontSize:
                                                                Adapt.px(26),
                                                            color: Colors.white,
                                                            fontWeight:
                                                                FontWeight.w600,
                                                            decoration:
                                                                TextDecoration
                                                                    .none),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                ],
                                              ),
                                            ),
                                            Container(
                                              width: Adapt.px(388),
                                              margin: EdgeInsets.only(
                                                  top: Adapt.px(10)),
                                              child: Row(
                                                children: [
                                                  Container(
                                                    margin: EdgeInsets.only(
                                                        left: Adapt.px(10),
                                                        right: Adapt.px(10)),
                                                    child: Text("施工区域:",
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                  Flexible(
                                                    child: Text(construction,
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                ],
                                              ),
                                            ),
                                            Container(
                                              width: Adapt.px(388),
                                              child: Row(
                                                children: [
                                                  Container(
                                                    margin: EdgeInsets.only(
                                                        left: Adapt.px(10),
                                                        right: Adapt.px(10)),
                                                    child: Text("拍摄时间:",
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                  Flexible(
                                                    child: Text(
                                                        new DateTime.now()
                                                            .toString()
                                                            .substring(0, 16),
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                ],
                                              ),
                                            ),
                                            Container(
                                              width: Adapt.px(388),
                                              child: Row(
                                                crossAxisAlignment:
                                                    CrossAxisAlignment.start,
                                                children: [
                                                  Container(
                                                    alignment:
                                                        Alignment.topLeft,
                                                    margin: EdgeInsets.only(
                                                        left: Adapt.px(10),
                                                        right: Adapt.px(10)),
                                                    child: Text("拍摄位置:",
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 1,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                  Flexible(
                                                    child: Text(
                                                        address == null
                                                            ? "定位中..."
                                                            : address,
                                                        style: TextStyle(
                                                          fontSize:
                                                              Adapt.px(26),
                                                          color: Colors.white,
                                                        ),
                                                        maxLines: 3,
                                                        overflow: TextOverflow
                                                            .ellipsis),
                                                  ),
                                                ],
                                              ),
                                            ),
                                          ],
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                              ),
                            )),
                        Offstage(
                            offstage: !_selectDefaulttext,
                            child: Container(
                                width: pics_width,
                                height: pics_height,
                                child: Stack(
                                  children: [
                                    DraggableWiget(
                                      widgetColor: Colors.transparent,
                                      margin_tu_width: margin_tu_width,
                                      margin_tu_height: margin_tu_height,
                                      picHeight: pics_height,
                                      picWidth: pics_width,
                                      rc_width: Adapt.px(400),
                                      rc_height: Adapt.px(200),
                                      hittext: hittext == null ? "暂无" : hittext,
                                      eventBus: eventBus,
                                    )
                                  ],
                                ))),
                        Offstage(
                          offstage: !_defaultHandRect,
                          child: Container(
                            width: pics_width,
                            height: pics_height,
                            child: HandReactBoard(
                                boardController: cosntrollerReact,
                                paintWidth: 5,
                                painColor: Colors.red,
                                width: pics_width,
                                height: pics_height,
                                type: selectReactType,
                                eventBusBiaoZhu: eventBusBiaoZhu),
                          ),
                        ),
                        Offstage(
                            offstage: !selectDefaultTuYa,
                            child: Container(
                              width: pics_width,
                              height: pics_height,
                              child: HandLineBoard(
                                  boardController: cosntrollerLine,
                                  paintWidth: 5,
                                  painColor: Colors.red,
                                  width: pics_width,
                                  height: pics_height,
                                  eventBusLine: eventBusLine),
                            ))
                      ],
                    ),
                  )),
                  Container(
                    height: Adapt.px(140),
                    color: ColorUtil.color141414,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        GestureDetector(
                          onTap: () {
                            showModalBottomSheet(
                                context: context,
                                isDismissible: true,
                                isScrollControlled: true,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(10),
                                ),
                                builder: (context) {
                                  return WatermarkPopupWindow(
                                      selectDeflutWater: _selectDeflutWater,
                                      eventBus: eventBus);
                                });
                          },
                          child: Container(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Image.asset(
                                  "images/icon_shuiyin.png",
                                  width: Adapt.px(60),
                                  height: Adapt.px(60),
                                  excludeFromSemantics: true,
                                  gaplessPlayback: true,
                                ),
                                Text('水印',
                                    style: TextStyle(
                                        fontSize: Adapt.px(32),
                                        color: Colors.white))
                              ],
                            ),
                            width: screenUtils.screenWidth / 4,
                          ),
                        ),
                        GestureDetector(
                          onTap: () {
                            showModalBottomSheet(
                                context: context,
                                isDismissible: true,
                                isScrollControlled: true,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(10),
                                ),
                                builder: (context) {
                                  return CanvasMarkPopupWindow(
                                      selectReactType: selectReactType,
                                      eventBus: eventBus);
                                });
                          },
                          child: Container(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Image.asset(
                                  "images/icon_biaozhu.png",
                                  width: Adapt.px(60),
                                  height: Adapt.px(60),
                                  excludeFromSemantics: true,
                                  gaplessPlayback: true,
                                ),
                                Text('标注',
                                    style: TextStyle(
                                        fontSize: Adapt.px(32),
                                        color: Colors.white))
                              ],
                            ),
                            width: screenUtils.screenWidth / 4,
                          ),
                        ),
                        GestureDetector(
                          onTap: () {
                            showModalBottomSheet(
                                context: context,
                                isDismissible: true,
                                isScrollControlled: true,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(10),
                                ),
                                builder: (context) {
                                  return HandTextPopupWindow(
                                      selectDefaulttext: _selectDefaulttext,
                                      eventBus: eventBus);
                                });
                          },
                          child: Container(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Image.asset(
                                  "images/icon_wenzi.png",
                                  width: Adapt.px(60),
                                  height: Adapt.px(60),
                                  excludeFromSemantics: true,
                                  gaplessPlayback: true,
                                ),
                                Text('文字',
                                    style: TextStyle(
                                        fontSize: Adapt.px(32),
                                        color: Colors.white))
                              ],
                            ),
                            width: screenUtils.screenWidth / 4,
                          ),
                        ),
                        GestureDetector(
                          onTap: () {
                            showModalBottomSheet(
                                context: context,
                                isDismissible: true,
                                isScrollControlled: true,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(10),
                                ),
                                builder: (context) {
                                  return HandTuYaPopupWindow(
                                      selectDefaultTuYa: selectDefaultTuYa,
                                      eventBus: eventBus);
                                });
                          },
                          child: Container(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Image.asset(
                                  "images/icon_tuya.png",
                                  width: Adapt.px(60),
                                  height: Adapt.px(60),
                                  excludeFromSemantics: true,
                                  gaplessPlayback: true,
                                ),
                                Text('涂鸦',
                                    style: TextStyle(
                                        fontSize: Adapt.px(32),
                                        color: Colors.white))
                              ],
                            ),
                            width: screenUtils.screenWidth / 4,
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ],
        ));
  }

  @override
  void onLocation(Map<String, Object> result) {
    if (result != null) {
      setState(() {
        address = result["address"];
      });
    }
  }

  _savePicUrl() async {
    EasyLoading.show(status: "上传中,请稍等...");
    RenderRepaintBoundary repaintBoundary =
        _handglobalKey.currentContext.findRenderObject();
    UI.Image image = await repaintBoundary.toImage(pixelRatio: 1.0);
    ByteData byteData = await image.toByteData(format: UI.ImageByteFormat.png);
    await AssetsLoadUtil.constant.imageByteFileUpload(
        byteData.buffer.asUint8List(), (List<Map<String, Object>> listAdress) {
      EasyLoading.dismiss();
      if (listAdress != null) {
        if (widget.typeEventBus != null) {
          widget.typeEventBus
              .fire(PicWaterUploadEvent(listAdress, widget.picInfomationModel));
          Navigator.pop(context);
        }
      }
    });
  }
}

DraggableLastWiget

import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:yirui_flutter_app/util/adapt_util.dart';
import 'package:yirui_flutter_app/util/color_util.dart';
import 'package:yirui_flutter_app/util/screen_utils.dart';
import 'package:yirui_flutter_app/dialog/handwrite/textinput_dialog.dart';

class DraggableLastWiget extends StatefulWidget {
  final Color widgetColor;

  ///计算图片距离顶部高度
  final double margin_tu_height;

  ///计算图片距离左侧距离
  final double margin_tu_width;

  ///矩形区域宽度
  final double rc_width;

  ///矩形区域高度
  final double rc_height;

  String hittext;

  final double xi_w;
  final double xi_h;
  final Offset draggLastoffset;

  DraggableLastWiget({
    Key key,
    this.widgetColor,
    this.margin_tu_height,
    this.margin_tu_width,
    this.rc_width,
    this.rc_height,
    this.hittext,
    this.xi_w,
    this.xi_h,
    this.draggLastoffset,
  }) : super(key: key);

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

class _DraggableLastWigetState extends State<DraggableLastWiget> {
  ScreenUtils screenUtils = null;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    screenUtils = ScreenUtils.getInstance();
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
        left: widget.draggLastoffset.dx*widget.xi_w,
        top: widget.draggLastoffset.dy*widget.xi_h,
        child: Draggable(
          data: widget.widgetColor,
          child: Container(
            width: widget.rc_width*widget.xi_w,
            height: widget.rc_height*widget.xi_h,
            color: widget.widgetColor,
            child: Opacity(
              opacity: 0.7,
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    margin: EdgeInsets.only(top: Adapt.px(23)*widget.xi_h),
                    child: Image.asset(
                      "images/icon_yuan.png",
                      width: Adapt.px(20)*widget.xi_w,
                      height: Adapt.px(20)*widget.xi_h,
                      excludeFromSemantics: true,
                      gaplessPlayback: true,
                    ),
                  ),
                  Flexible(
                      child: Stack(
                        children: [
                          Container(
                            margin: EdgeInsets.only(top: Adapt.px(13)*widget.xi_h),
                            child: Image.asset(
                              "images/arrow_icon.png",
                              width: Adapt.px(40)*widget.xi_w,
                              height: Adapt.px(40)*widget.xi_h,
                              excludeFromSemantics: true,
                              gaplessPlayback: true,
                            ),
                          ),
                          Container(
                              child: Stack(
                                children: [
                                  Container(
                                    margin: EdgeInsets.only(left: Adapt.px(30)*widget.xi_w),
                                    decoration: new BoxDecoration(
                                      color: ColorUtil.color2c2c2c,
                                      borderRadius: BorderRadius.all(
                                          Radius.circular(Adapt.px(25)*widget.xi_w)),
                                      border: new Border.all(
                                          width: Adapt.px(5)*widget.xi_w,
                                          color: ColorUtil.color2c2c2c),
                                    ),
                                  ),
                                  Container(
                                    margin: EdgeInsets.only(left: Adapt.px(35)*widget.xi_w),
                                    child: Text(
                                      widget.hittext,
                                      style: TextStyle(
                                          fontSize: Adapt.px(28)*widget.xi_w,
                                          color: Colors.white),
                                      maxLines: 5,
                                      overflow: TextOverflow.ellipsis,
                                    ),
                                  ),
                                ],
                              ))
                        ],
                      ))
                ],
              ),
            ),
          ),
          feedback: Container(),
        ));
  }
}

HandReactLastBoard

import 'dart:typed_data';
import 'dart:ui';
import 'dart:ui' as UI;

import 'package:event_bus/event_bus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:yirui_flutter_app/event/base_event.dart';
import 'package:yirui_flutter_app/event/handbiaozhu_event.dart';

class HandReactLastBoard extends StatefulWidget {
  ///手写笔颜色
  final Color painColor;

  ///手写笔宽度
  final double paintWidth;

  ///手写笔控制器
  final HandReactLastBoardController boardController;

  final double width;
  final double height;

  final int type; //绘制矩形还是绘制椭圆

  final EventBus eventBusBiaoZhu;
  final double xi_w;
  final double xi_h;

  HandReactLastBoard({
    Key key,
    this.painColor,
    this.paintWidth,
    @required this.boardController,
    this.width,
    this.height,
    this.type,
    this.eventBusBiaoZhu,
    this.xi_w,
    this.xi_h,
  }) : super(key: key);

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

class _HandReactLastBoardState extends State<HandReactLastBoard> {
  List<Rectangular> _strokes = [];
  List<TheEllipse> _theEllipse = [];
  bool isClear = false;

  @override
  void initState() {
    super.initState();
    widget.boardController.bindContext(context);
    widget.eventBusBiaoZhu.on<HandCavasXYEvent>().listen((event) {
      if (event.obj["type"] == 2) {
        if (mounted) {
          DragUpdateDetails details = event.obj["obj"];
          if (widget.type == 1) {
            setState(() {
              _strokes.last.updateStartX = details.localPosition.dx;
              _strokes.last.updateStartY = details.localPosition.dy;
            });
            widget.boardController.refRectStrokes(_strokes);
          } else if (widget.type == 2) {
            setState(() {
              _theEllipse.last.updateStartX = details.localPosition.dx;
              _theEllipse.last.updateStartY = details.localPosition.dy;
            });
            widget.boardController.refEllipseStrokes(_theEllipse);
          }
        }
      } else if (event.obj["type"] == 1) {
        double startX = event.obj["startX"];
        double startY = event.obj["startY"];
        if (widget.type == 1) {
          final newStroke = Rectangular(
            color: widget.painColor,
            width: widget.paintWidth,
            startX: startX,
            startY: startY,
            isClear: isClear,
          );
          _strokes.add(newStroke);
          widget.boardController.refRectStrokes(_strokes);
        } else if (widget.type == 2) {
          final newStroke = TheEllipse(
            color: widget.painColor,
            width: widget.paintWidth,
            startX: startX,
            startY: startY,
            isClear: isClear,
          );
          _theEllipse.add(newStroke);
          widget.boardController.refEllipseStrokes(_theEllipse);
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: BoardPainter(
          strokes: _strokes,
          theellipse: _theEllipse,
          type: widget.type,
          width: widget.width,
          height: widget.height,
          xi_w: widget.xi_w,
          xi_h: widget.xi_h),
      size: Size.infinite,
    );
  }
}

class HandReactLastBoardController extends ChangeNotifier {
  BuildContext _context;
  List<Rectangular> strokes = [];
  List<TheEllipse> ellipses = [];

  void bindContext(BuildContext context) {
    _context = context;
  }

  void refRectStrokes(List<Rectangular> newValue) {
    if (strokes != newValue) {
      strokes = newValue;
    }
    notifyListeners();
  }

  void refEllipseStrokes(List<TheEllipse> newtheellipse) {
    if (ellipses != newtheellipse) {
      ellipses = newtheellipse;
    }
    notifyListeners();
  }

  void clearBoard() {
    strokes.clear();
    ellipses.clear();
    notifyListeners();
  }
}

///矩形方框
class Rectangular {
  final Color color;
  final double startX;
  final double startY;
  double updateStartX;
  double updateStartY;
  final bool isClear;
  final double width;

  Rectangular({
    this.color = Colors.black,
    this.width = 4,
    this.isClear = false,
    this.startX = 0,
    this.startY = 0,
    this.updateStartX = 0,
    this.updateStartY = 0,
  });
}

///椭圆
class TheEllipse {
  final Color color;
  final double startX;
  final double startY;
  double updateStartX;
  double updateStartY;
  final bool isClear;
  final double width;

  TheEllipse({
    this.color = Colors.black,
    this.width = 4,
    this.isClear = false,
    this.startX = 0,
    this.startY = 0,
    this.updateStartX = 0,
    this.updateStartY = 0,
  });
}

class BoardPainter extends CustomPainter {
  final List<Rectangular> strokes;
  final List<TheEllipse> theellipse;
  final int type;
  final double height;
  final double width;
  final double xi_w;
  final double xi_h;

  BoardPainter({
    this.type,
    this.strokes,
    this.theellipse,
    this.height,
    this.width,
    this.xi_w,
    this.xi_h,
  });

  @override
  void paint(Canvas canvas, Size size) {
    canvas.clipRect(Rect.fromLTWH(0, 0, width*xi_w, height*xi_h));

    canvas.drawRect(
      Rect.fromLTWH(0, 0, width*xi_w, height*xi_h),
      Paint()..color = Colors.transparent,
    );
    canvas.saveLayer(Rect.fromLTWH(0, 0, width*xi_w, height*xi_h), Paint());

    if (type == 1) {
      ///绘制矩形
      for (final stroke in strokes) {
        if (stroke.updateStartX != null && stroke.updateStartX > 0) {
          if (stroke.updateStartY != null && stroke.updateStartY > 0) {
            final paint = Paint()
              ..strokeWidth = stroke.width*xi_w
              ..color = stroke.isClear ? Colors.transparent : stroke.color
              ..strokeCap = StrokeCap.round
              ..style = PaintingStyle.stroke
              ..blendMode =
                  stroke.isClear ? BlendMode.clear : BlendMode.srcOver;
            canvas.drawRect(
              Rect.fromLTWH(
                  stroke.startX*xi_w,
                  stroke.startY*xi_h,
                  stroke.updateStartX*xi_w - stroke.startX*xi_w,
                  stroke.updateStartY*xi_h - stroke.startY*xi_h),
              paint,
            );
          }
        }
      }
    } else if (type == 2) {
      ///绘制椭圆
      for (final theell in theellipse) {
        if (theell.updateStartX != null && theell.updateStartX > 0) {
          if (theell.updateStartY != null && theell.updateStartY > 0) {
            final paint = Paint()
              ..strokeWidth = theell.width*xi_w
              ..color = theell.isClear ? Colors.transparent : theell.color
              ..strokeCap = StrokeCap.round
              ..style = PaintingStyle.stroke
              ..blendMode =
                  theell.isClear ? BlendMode.clear : BlendMode.srcOver;
            canvas.drawOval(
              Rect.fromPoints(
                  Offset(
                    theell.startX*xi_w,
                    theell.startY*xi_h,
                  ),
                  Offset(theell.updateStartX*xi_w, theell.updateStartY*xi_h)),
              paint,
            );
          }
        }
      }
    }

    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

HandLineLastBoard

import 'dart:ui';
import 'dart:ui' as UI;

import 'package:event_bus/event_bus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:yirui_flutter_app/event/base_event.dart';
import 'package:yirui_flutter_app/event/handbiaozhu_event.dart';

class HandLineLastBoard extends StatefulWidget {
  ///手写笔颜色
  final Color painColor;

  ///手写笔宽度
  final double paintWidth;

  ///手写笔控制器
  final HandLineBoardLastController boardController;

  final double width;
  final double height;

  final EventBus eventBusLine;
  final double xi_w;
  final double xi_h;

  HandLineLastBoard({
    Key key,
    this.painColor,
    this.paintWidth,
    @required this.boardController,
    this.width,
    this.height,
    this.eventBusLine,
    this.xi_w,
    this.xi_h,
  }) : super(key: key);

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

class _HandLineLastBoardState extends State<HandLineLastBoard> {
  List<Stroke> _strokes = [];
  bool isClear = false;
  double starty = 0;

  @override
  void initState() {
    super.initState();
    widget.boardController.bindContext(context);
    widget.eventBusLine.on<HandCavasXYEvent>().listen((event) {
      if (event.obj["type"] == 2) {
        if (mounted) {
          DragUpdateDetails details = event.obj["obj"];
          setState(() {
            _strokes.last.path.lineTo(
                details.localPosition.dx*widget.xi_w, details.localPosition.dy*widget.xi_h - starty*widget.xi_h);
          });
          widget.boardController.refStrokes(_strokes);
        }
      } else if (event.obj["type"] == 1) {
        double startX = event.obj["startX"];
        double startY = event.obj["startY"];
        final newStroke = Stroke(
          color: widget.painColor,
          width: widget.paintWidth,
          isClear: isClear,
        );
        newStroke.path.moveTo(startX*widget.xi_w, startY*widget.xi_h);
        _strokes.add(newStroke);
        widget.boardController.refStrokes(_strokes);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: BoardPainter(
          strokes: _strokes,
          width: widget.width,
          height: widget.height,
          xi_w: widget.xi_w,
          xi_h: widget.xi_h),
      size: Size.infinite,
    );
  }
}

class HandLineBoardLastController extends ChangeNotifier {
  BuildContext _context;
  List<Stroke> strokes = [];

  void bindContext(BuildContext context) {
    _context = context;
  }

  Future<UI.Image> get uiImage {
    UI.PictureRecorder recorder = UI.PictureRecorder();
    Canvas canvas = Canvas(recorder);
    BoardPainter painter = BoardPainter();
    Size size = _context.size;
    painter.paint(canvas, size);
    return recorder
        .endRecording()
        .toImage(size.width.floor(), size.height.floor());
  }

  void refStrokes(List<Stroke> newValue) {
    if (strokes != newValue) {
      strokes = newValue;
    }
    notifyListeners();
  }

  void clearBoard() {
    strokes.clear();
    notifyListeners();
  }
}

class Stroke {
  final path = Path();
  final Color color;
  final double width;
  final bool isClear;

  Stroke({
    this.color = Colors.black,
    this.width = 4,
    this.isClear = false,
  });
}

class BoardPainter extends CustomPainter {
  final List<Stroke> strokes;
  final double height;
  final double width;
  final double xi_w;
  final double xi_h;

  BoardPainter({
    this.strokes,
    this.height,
    this.width,
    this.xi_w,
    this.xi_h,
  });

  @override
  void paint(Canvas canvas, Size size) {
    canvas.clipRect(Rect.fromLTWH(0, 0, width*xi_w, height*xi_h));

    canvas.drawRect(
      Rect.fromLTWH(0, 0, width*xi_w, height*xi_h),
      Paint()..color = Colors.transparent,
    );
    canvas.saveLayer(Rect.fromLTWH(0, 0, width*xi_w, height*xi_h), Paint());

    for (final stroke in strokes) {
      final paint = Paint()
        ..strokeWidth = stroke.width*xi_w
        ..color = stroke.isClear ? Colors.transparent : stroke.color
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke
        ..blendMode = stroke.isClear ? BlendMode.clear : BlendMode.srcOver;
      canvas.drawPath(stroke.path, paint);
    }
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

以上代码便是主体代码,接下来我解释下我的思路以及为什么要这么做,如果有好的方案欢迎评论。
可以看到我的绘制区域宽高一开始就固定了大小,那么图片宽高由图片真实宽高到绘制区域宽高去适配,缩放到一个相对比例的Widget,上下分为了三层,由下往上是真实图片大小区,遮挡层,操作区,由操作区操作的动作同步到真实图片大小区,最后上传时直接把真实大小图层区转图片上传,这时可能会好奇的问为什么不将操作好的Widget在点击保存时候再缩放到真实大小图层的图片上传,一开始我是这么做的,但Widget转图片过程是耗时的,体验很差。

RenderRepaintBoundary repaintBoundary =
        _handglobalKey.currentContext.findRenderObject();
    UI.Image image = await repaintBoundary.toImage(pixelRatio: 1.0);
    ByteData byteData = await image.toByteData(format: UI.ImageByteFormat.png);

这一步是非常耗时的,为了减少这部分逻辑,只能在操作时候,相当于在看不到的view层进行模拟操作了所有动作,而保存实际避免了两个问题:1.图片转换过程中耗时问题 2.图片失真问题(图片放到到真实大小图片和真实大小区域绘制相同区域是不一样的)。
以下是我实现的效果。有问题欢迎评论留言。


device-2021-06-26-105821.png
device-2021-06-26-105852.png
device-2021-06-26-105949.png
device-2021-06-26-110035.png
device-2021-06-26-110110.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容