Flutter 自定义控件——图片裁剪框

文章目录

  • 前言
  • 效果图
  • 源代码
      • DashLineWidget.dart
      • CornerImageWidget.dart
      • CorpImageWidget.dart
  • CSDN C币赞助下载(5C币)
  • 后记
  • 参考文章

前言

转载烦请附加链接,以尊重本人开发成果。
博客编辑时间有限,简单粗暴。

效果图

虚线颜色宽度间距等所有属性都可以自定义。
Flutter 自定义控件——图片裁剪框_第1张图片

源代码

只需要三个文件总共大概500行左右。
另外需要自备4张触点图片。

DashLineWidget.dart

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'CorpImageWidget.dart';

// ignore: must_be_immutable
class DashLineWidget extends StatefulWidget{
  final Key key;
  Paint painter;
  final Point p1,p2;
  DashLineWidget(this.key,this.p1,this.p2,this.painter):super(key:key);
  @override
  State<DashLineWidget> createState() => DashLineState(this.p1,this.p2,this.painter);
}

class DashLineState extends State<DashLineWidget>{
  Paint painter;
  final Point p1,p2;
  DashLineState(this.p1,this.p2,this.painter);

  final double dashWidth = 5;
  final double gapWidth = 3;
  double startGap = 0;

  Timer _timer;
  void _startTimer(){
    if(_timer!=null) return;
    _timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
      setState(() {
        startGap+=0.5;
        startGap%=(dashWidth+gapWidth);
      });
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _startTimer();
    return CustomPaint(painter: DashLinePainter(p1.offset(),p2.offset(),painter,startGap,dashWidth,gapWidth),);
  }

}

/// 线段画笔
class DashLinePainter extends CustomPainter {
  //double和0比较的精度差,如果在这个精度里,那么这个值就当做0
  final double accuracy = 0.000001;
  //实线长度
  final double dashLineLength;
  //起始绘制长度[0,dashLineLength+gapLineLength] 如果超过虚线那么剩下的长度用来绘制起始实线
  final double startGapLength;
  //虚线长度
  final double gapLineLength;
  //起始绘制点
  final Offset p1;
  //终止绘制点
  final Offset p2;
  //y = kx + b 由p1和p2确定k、b的值
  double k;
  double b;

  final Paint painter;

  DashLinePainter(this.p1,this.p2,this.painter,[this.startGapLength = 0,this.dashLineLength = 5,this.gapLineLength=2]){
    double y1 = p1.dy, y2 = p2.dy;
    double x1 = p1.dx, x2 = p2.dx;
    this.k = (y1-y2) / (x1-x2);
    this.b = (y1*x2 - y2*x1)/(x2 - x1);
  }

  void drawLine(Canvas canvas, Offset p1,Offset p2){
    // print("drawline: $p1 $p2");
    canvas.drawLine(p1, p2, painter);
  }

  @override
  void paint(Canvas canvas, Size size) {
    if(k.abs()<accuracy){
      drawHrizontalLine(canvas);
      return;
    }
    if(k.isNaN||k.isInfinite){
      drawVerticalLine(canvas);
      return;
    }
    drawNormalLine(canvas);
  }

  void drawNormalLine(Canvas canvas){
    print("DashLinePainter -> drawNormalLine $k $p1 $p2");
    double radians = atan(k);
    double deataY = sin(radians);
    double deataX = cos(radians);
    bool isDecreaseX = p1.dx>p2.dx;
    bool isDecreaseY = p1.dy>p2.dy;

    if(k<0&&!isDecreaseY || k>0&&isDecreaseY){
      deataY = -deataY;
    }
    if(isDecreaseX) deataX = -deataX;
    double gapX = gapLineLength * deataX, gapY = gapLineLength * deataY;
    double dashX = dashLineLength * deataX, dashY = dashLineLength * deataY;
    double x = p1.dx;
    double y = p1.dy;

    if(startGapLength>gapLineLength){
      x += (startGapLength - gapLineLength) * deataX;
      y += (startGapLength - gapLineLength) * deataY;
      drawLine(canvas, Offset(p1.dx,p1.dy),Offset(isDecreaseX?max(x,p2.dx):min(x,p2.dx),isDecreaseY?max(y,p2.dy):min(y,p2.dy)));
      x += gapLineLength * deataX;
      y += gapLineLength * deataY;
    }
    else{
      x += startGapLength*deataX;
      y += startGapLength*deataY;
    }
    while(((!isDecreaseX)&&(x<=p2.dx)) || (isDecreaseX&&(x>=p2.dx)) ){
      if((!isDecreaseX)&&(x + dashX < p2.dx) || isDecreaseX&&(x+dashX>p2.dx)){
        drawLine(canvas, Offset(x,y),Offset(x+dashX,y+dashY));
      }
      else{
        drawLine(canvas, Offset(x,y),Offset(p2.dx,p2.dy),);
      }
      x += dashX;
      y += dashY;
      x += gapX;
      y += gapY;
    }
  }

  void drawHrizontalLine(Canvas canvas){
    print("DashLinePainter -> drawHrizontalLine $p1 $p2");
    double x = p1.dx;
    bool isDecrease = p1.dx>p2.dx;
    double startGapX = isDecrease?-startGapLength:startGapLength;
    double gapX = isDecrease?-gapLineLength:gapLineLength;
    double dashX = isDecrease?-dashLineLength:dashLineLength;

    if(startGapLength>gapLineLength){
      x += startGapX - gapX;
      drawLine(canvas, Offset(p1.dx,p1.dy),Offset(isDecrease?max(p2.dx,x):min(p2.dx,x),p1.dy),);
      x += gapX;
    }
    else{
      x += startGapX;
    }
    while(!isDecrease&&x<=p2.dx|| isDecrease&&x>=p2.dx){
      drawLine(canvas,Offset(x,p1.dy),Offset(isDecrease?max(x+dashX,p2.dx):min(x+dashX,p2.dx),p1.dy),);
      x+=dashX;
      x+=gapX;
    }
  }

  void drawVerticalLine(Canvas canvas){
    print("DashLinePainter -> drawVerticalLine $p1 $p2");
    double y = p1.dy;
    bool isDecrease = p1.dy>p2.dy;
    double startGapY = isDecrease?-startGapLength:startGapLength;
    double gapY = isDecrease?-gapLineLength:gapLineLength;
    double dashY = isDecrease?-dashLineLength:dashLineLength;

    if(startGapLength>gapLineLength){
      y += startGapY - gapY;
      drawLine(canvas, Offset(p1.dx,p1.dy),Offset(p1.dx,isDecrease?max(p2.dy,y):min(p2.dy,y)),);
      y += gapY;
    }
    else{
      y += startGapY;
    }
    while(!isDecrease&&y<=p2.dy || isDecrease&&y>=p2.dy){
      drawLine(canvas, Offset(p1.dx,y),Offset(p1.dx,isDecrease?max(p2.dy,y+dashY):min(p2.dy,y+dashY)),);
      y += dashY;
      y += gapY;
    }
  }

  @override
  bool shouldRepaint(DashLinePainter oldDelegate) => true;
}

CornerImageWidget.dart

import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'CorpImageWidget.dart';

class CornerImageWidget extends StatefulWidget{
  final Key key;
  final String image;
  final Point p;
  final Paint painter;
  CornerImageWidget(this.key,this.image,this.painter,this.p):super(key:key);

  @override
  State<CornerImageWidget> createState(){
    return CornerImageState(this.image,this.painter,this.p);
  }
}

class CornerImageState extends State<CornerImageWidget>{
  final String image;
  final Point p;
  final Paint painter;
  ui.Image imgData;

  Future<ui.Image> load(String asset) async {
    ByteData data = await rootBundle.load(asset);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  @override
  void initState() {
    super.initState();
    print("CornerImageWidget $image");
    load(image).then((value){setState(() {
      imgData = value;
    });});
  }

  CornerImageState(this.image,this.painter,this.p);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(painter: CornerImagePainter(imgData,painter,p.offset()),);
  }
}

class CornerImagePainter extends CustomPainter {
  final ui.Image image;
  final Paint painter;
  final Offset p;
  CornerImagePainter(this.image,this.painter,this.p);

  @override
  void paint(Canvas canvas, Size size) {
    if(image!=null){
      canvas.drawImageRect(image,
          Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
          Rect.fromLTWH(p.dx-10, p.dy-10, 20, 20),
          painter);
    }
  }

  @override
  bool shouldRepaint(CornerImagePainter oldDelegate) {
    return oldDelegate.p!=p||oldDelegate.image!=image;
  }
}

CorpImageWidget.dart

import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'CornerImageWidget.dart';
import 'DashLineWidget.dart';

class Point{
  double x,y;
  Point([this.x = 0,this.y = 0]);
  Offset offset()=>Offset(x,y);
  void update(Offset offset){
    x = offset.dx;
    y = offset.dy;
  }
  @override
  String toString() => "($x,$y)";
}

class CropImageWidget extends StatefulWidget{
  @override
  State<CropImageWidget> createState() => _CropImageState();
}

class _CropImageState extends State<CropImageWidget>{
  static const Offset _TL = Offset(100,100);
  static const Offset _TR = Offset(200,100);
  static const Offset _BL = Offset(100,200);
  static const Offset _BR = Offset(200,200);

  Paint painter;
  //静止状态下的offset
  Offset idleOffset=Offset(0, 0);
  //本次移动的offset
  Offset moveOffset=Offset(0, 0);
  //最后一次down事件的offset
  Offset lastStartOffset=Offset(0, 0);

  final Point pTL = Point();
  final Point pTR = Point();
  final Point pBL = Point();
  final Point pBR = Point();

  ///当前widget所绘制虚线的状态 用于更新移动时所相关的线
  final GlobalKey<DashLineState> kT = GlobalKey();//top
  final GlobalKey<DashLineState> kR = GlobalKey();//right
  final GlobalKey<DashLineState> kB = GlobalKey();//bottom
  final GlobalKey<DashLineState> kL = GlobalKey();//left
  ///当前widget所绘制移动触点的状态 用于更新移动点
  final GlobalKey<CornerImageState> kTL = GlobalKey();//top left
  final GlobalKey<CornerImageState> kTR = GlobalKey();//top right
  final GlobalKey<CornerImageState> kBL = GlobalKey();//bottom left
  final GlobalKey<CornerImageState> kBR = GlobalKey();//bottom right

  //region 选择移动点
  Point p;
  List<GlobalKey> g;
  List<Point> lstPoints;
  List<List<GlobalKey>> lstStates;

  ui.Image imgTL,imgTR,imgBL,imgBR;

  void selectPoint(Offset offset){
    // print("     selectPoint 1");
    if(isSelectedPoint()) return;
    double x = offset.dx;
    double y = offset.dy;
    for(int i=0;i<4;++i){
      Point _p = lstPoints[i];
      double x1 = _p.x - 10;
      double x2 = _p.x + 10;
      double y1 = _p.y - 10;
      double y2 = _p.y + 10;
      if(x>=x1&&x<=x2&&y>=y1&&y<=y2){
        p = _p;
        g = lstStates[i];
        lastStartOffset = offset;
        idleOffset = _p.offset();
        // print("     selectPoint 2");
        break;
      }
    }
  }

  bool isSelectedPoint() => p!=null;

  void updatePoint(Offset offset){
    // print("     updatePoint 1");
    if(isSelectedPoint()){
      moveOffset=offset-lastStartOffset+idleOffset;
      moveOffset=Offset(max(0, moveOffset.dx), max(0, moveOffset.dy));
      p.update(moveOffset);
      for(GlobalKey k in g){
        k.currentState.setState(() {});
      }
      // print("     updatePoint 2  $p ");
      // print("     updatePoint 3 $pTL, $pTR, $pBL, $pBR ");
    }
  }

  void releasePoint(){
    // print("     releasePoint");
    p = null;
  }
  //endregion

  // ignore: non_constant_identifier_names
  _CropImageState([Offset TL = _TL,Offset TR = _TR, Offset BL = _BL, Offset BR = _BR]){
    pTL.update(TL);
    pTR.update(TR);
    pBL.update(BL);
    pBR.update(BR);
    lstPoints = [pTL, pTR, pBL, pBR];
    lstStates = [
      [kTL,kT,kL],
      [kTR,kT,kR],
      [kBL,kB,kL],
      [kBR,kB,kR],
    ];
  }

  @override
  void initState(){
    painter = Paint()
                ..color = Color(0xffef6c00)
                ..style = PaintingStyle.fill
                ..isAntiAlias = true
                ..filterQuality = FilterQuality.high
                ..strokeWidth = 1.5;
    super.initState();
  }

  @override
  void deactivate() {
    super.deactivate();
    releasePoint();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: GestureDetector(
        onPanStart: (detail) {
          // print("onPanStart ${detail.globalPosition}");
          selectPoint(detail.globalPosition);
        },
        onPanUpdate: (detail){
          // print("onPanUpdate ${detail.globalPosition}");
          updatePoint(detail.globalPosition);
        },
        onPanEnd: (detail) {
          // print("onPanEnd");
          releasePoint();
        },
        child: Ink(
          child: Stack(
            textDirection: TextDirection.ltr,
            children: <Widget>[
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: DashLineWidget(kT,pTL,pTR,painter),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: DashLineWidget(kR,pTR,pBR,painter),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: DashLineWidget(kB,pBR,pBL,painter),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: DashLineWidget(kL,pBL,pTL,painter),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: CornerImageWidget(kTL,'assets/images/ic_fixation_tl.png', painter, pTL),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: CornerImageWidget(kTR,'assets/images/ic_fixation_tr.png', painter, pTR),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: CornerImageWidget(kBL,'assets/images/ic_fixation_bl.png', painter, pBL),
              ),
              Positioned(left: 0,right: 0,top: 0,bottom: 0,
                child: CornerImageWidget(kBR,'assets/images/ic_fixation_br.png', painter, pBR)
              ),
            ],
          ),
        ),
      )
    );
  }
}

CSDN C币赞助下载(5C币)

包含整个小项目的全部代码。
当然整个项目只需要以上提及的三个dart文件,以及展示的图片,无需下载也可直接使用,下载仅是直观使用或者便捷复制。
CSDN下载:https://download.csdn.net/download/best335/14092462

后记

代码冗余度还有很大的优化空间。

参考文章

启发:https://blog.csdn.net/PrinceBB/article/details/108133286
控件拖动:https://blog.csdn.net/act64/article/details/89889266
画笔使用:https://blog.csdn.net/u011272795/article/details/83828732
assets图片加载成ui.Image:https://blog.csdn.net/qq_34476727/article/details/107519603

你可能感兴趣的:(Flutter,flutter,自定义控件,图片,裁剪)