Flutter圖片添加水印,矩形或文字標注,自定義涂鴉到底應該怎么做?

前段時間參照今日相機有個需求,具體需求如下:
1.拍照或者相冊選擇圖片在編輯時候可以添加一個自定義的水印(包含時間和定位信息);
2.能在圖片上面繪制矩形或者橢圓;
3.能在圖片上面編輯文字標注,文字標注區(qū)域可以拖動;
4.可以自定義涂鴉;
Flutter也有很多庫,但全網好像并沒有此類的庫,那就自己動手實現。因為公司項目,我并沒有整理Demo出來。接下來我主要把自己的一些思路整理出來,也會放一些片段式的代碼。

可能會遇到的問題:

  • 拍照圖片和相冊圖片編輯區(qū)域適配問題?
    *繪制區(qū)域到底由什么決定?
    *整個過程會經歷網絡圖片到本地,再從本地編輯之后上傳,上傳后失真問題
    *圖片修改上傳后與自己標注不成比例問題
    *文字標注拖動及邊界界定問題
    *多個圖層同時進行操作可能會遇到的問題

拍照入口

需要引入的包

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 {
  ///可繪制區(qū)域背景真實高度
  double canvasbg_height = 0;

  ///可繪制區(qū)域背景真實寬度
  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;

  ///水印施工區(qū)域
  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("施工區(qū)域:",
                                                              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("施工區(qū)域:",
                                                        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;

  ///矩形區(qū)域寬度
  final double rc_width;

  ///矩形區(qū)域高度
  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;
  }
}

以上代碼便是主體代碼,接下來我解釋下我的思路以及為什么要這么做,如果有好的方案歡迎評論。
可以看到我的繪制區(qū)域寬高一開始就固定了大小,那么圖片寬高由圖片真實寬高到繪制區(qū)域寬高去適配,縮放到一個相對比例的Widget,上下分為了三層,由下往上是真實圖片大小區(qū),遮擋層,操作區(qū),由操作區(qū)操作的動作同步到真實圖片大小區(qū),最后上傳時直接把真實大小圖層區(qū)轉圖片上傳,這時可能會好奇的問為什么不將操作好的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.圖片失真問題(圖片放到到真實大小圖片和真實大小區(qū)域繪制相同區(qū)域是不一樣的)。
以下是我實現的效果。有問題歡迎評論留言。


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
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容