Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Flutter给图片添加涂鸦

Flutter实现给图片添加涂鸦功能

作者:如此风景

这篇文章主要介绍了利用Flutter实现给图片添加涂鸦功能,文中通过代码示例给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

简介

先来张图,看一下最终效果

关闭和确定

颜色选择

撤销功能

清除功能

涂鸦图片的放大和缩小

放大缩小后按照新的线条粗细继续涂鸦

保存涂鸦图片到本地。

代码介绍

涂鸦颜色选择组件。

主要是显示为可配置的圆点和外圈

circle_ring_widget.dart

import 'package:flutter/material.dart';

class CircleRingWidget extends StatelessWidget {
  late bool isShowRing;
  late Color dotColor;
  CircleRingWidget(this.isShowRing,this.dotColor, {super.key});
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: CircleAndRingPainter(isShowRing,dotColor),
      size: const Size(56.0, 81.0), // 调整尺寸大小
    );
  }
}

class CircleAndRingPainter extends CustomPainter {
  late bool isShowRing;
  late Color dotColor;
  CircleAndRingPainter(this.isShowRing,this.dotColor);
  @override
  void paint(Canvas canvas, Size size) {
    Paint circlePaint = Paint()
      ..color = dotColor // 设置圆点的颜色
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 1.0;

    Paint ringPaint = Paint()
      ..color = Colors.white // 设置圆环的颜色
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 1.0
      ..style = PaintingStyle.stroke;

    Offset center = size.center(Offset.zero);

    // 画一个半径为10的圆点
    canvas.drawCircle(center, 13.0, circlePaint);

    if(isShowRing){
      // 画一个半径为20的圆环
      canvas.drawCircle(center, 18.0, ringPaint);
    }
  }

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

存储颜色和食指划过点的数据类

color_offset.dart

import 'dart:ui';

class ColorOffset{
  late Color color;
  late List<Offset> offsets=[];
  ColorOffset(Color color,List<Offset> offsets){
    this.color=color;
    this.offsets.addAll(offsets);
  }
}

具体涂鸦点的绘制

此处是具体绘制涂鸦点的自定义view。大家是不是觉得哇,好简单呢。两个循环一嵌套,瞬间所有涂鸦就都出来了。其实做这个功能时,我参考了其他各种涂鸦控件,但是总觉得流程非常复杂。难以理解。原因是他们的颜色和点在数据层面都是混合到一起的,而且还得判断哪里是新画的涂鸦线条,来回控制。用这个demo的结构,相信各位读者一看就能知道里面的思路

doodle_painter.dart

import 'package:flutter/cupertino.dart';

import 'color_offset.dart';

class DoodleImagePainter extends CustomPainter {
  late Map<int,ColorOffset> newPoints;
  DoodleImagePainter(this.newPoints);

  @override
  void paint(Canvas canvas, Size size) {
    newPoints.forEach((key, value) {
      Paint paint = _getPaint(value.color);
      for(int i=0;i<value.offsets.length - 1;i++){
        //最后一个画点,其他画线
        if(i==value.offsets.length-1){
          canvas.drawCircle(value.offsets[i], 2.0, paint);
        }else{
          canvas.drawLine(value.offsets[i], value.offsets[i + 1], paint);
        }

      }
    });
  }
  Paint _getPaint(Color color){
    return Paint()
      ..color = color
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5.0;
  }

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

涂鸦主要界面代码

下面这些就是整体涂鸦相关功能代码,其中一些资源图片未提供,请根据需要自己去设计处获取。

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

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

import '../player_base_control.dart';
import 'circle_ring_widget.dart';
import 'color_offset.dart';
import 'doodle_painter.dart';

class DoodleWidget extends PlayerBaseControllableWidget {
  final String snapShotPath;
  final ValueChanged<bool>? completed;
  const DoodleWidget(super.controller,
      {super.key, required this.snapShotPath, this.completed});

  @override
  State<StatefulWidget> createState() => _DoodleWidgetState();
}

class _DoodleWidgetState extends State<DoodleWidget> {
  Map<int, ColorOffset> newPoints = {};
  List<Offset> points = [];
  int lineIndex = 0;
  GlobalKey globalKey = GlobalKey();
  int currentSelect = 0;
  final double maxScale = 3.0;
  final double minScale = 1.0;
  List<Color> colors = const [
    Color(0xffff0000),
    Color(0xfffae03d),
    Color(0xff6f52ff),
    Color(0xffffffff),
    Color(0xff000000)
  ];
  TransformationController controller = TransformationController();
  double realScale = 1.0;
  Offset realTransLocation = Offset.zero;
  late Image currentImg;

  bool isSaved = false;

  @override
  void initState() {
    currentImg = Image.memory(File(widget.snapShotPath).readAsBytesSync());
    controller.addListener(() {
      ///获取矩阵里面的缩放具体值
      realScale = controller.value.entry(0, 0);

      ///获取矩阵里面的位置偏移量
      realTransLocation = Offset(controller.value.getTranslation().x,
          controller.value.getTranslation().y);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned.fill(child: LayoutBuilder(
            builder: (BuildContext context, BoxConstraints constraint) {
          return InteractiveViewer(
            panEnabled: false,
            scaleEnabled: true,
            maxScale: maxScale,
            minScale: minScale,
            transformationController: controller,
            onInteractionStart: (ScaleStartDetails details) {
              // print("--------------onInteractionStart执行了  dx=${details.focalPoint.dx} dy=${details.focalPoint.dy}");
            },
            onInteractionUpdate: (ScaleUpdateDetails details) {
              if (details.pointerCount == 1) {
                /// 获取 x,y 拿到值后进行缩放偏移等换算
                var x = details.focalPoint.dx;
                var y = details.focalPoint.dy;
                var point = Offset(
                    _getScaleTranslateValue(x, realScale, realTransLocation.dx),
                    _getScaleTranslateValue(
                        y, realScale, realTransLocation.dy));
                setState(() {
                  points.add(point);
                  newPoints[lineIndex] =
                      ColorOffset(colors[currentSelect], points);
                });
              }
            },
            onInteractionEnd: (ScaleEndDetails details) {
              // print("onInteractionEnd执行了");
              if (points.length > 5) {
                newPoints[lineIndex] =
                    ColorOffset(colors[currentSelect], points);
                lineIndex++;
              }
              // newPoints.addAll({lineIndex:ColorOffset(colors[currentSelect],points)});
              //清空原数组
              points.clear();
            },
            child: RepaintBoundary(
              key: globalKey,
              child: Stack(
                alignment: AlignmentDirectional.center,
                children: [
                  Positioned.fill(child: currentImg),
                  Positioned.fill(
                      child:
                          CustomPaint(painter: DoodleImagePainter(newPoints))),
                ],
              ),
            ),
          );
        })),
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: _bottomActions(),
        )
      ],
    );
  }

  double _getScaleTranslateValue(
      double current, double scale, double translate) {
    return current / scale - translate / scale;
  }

  Widget _bottomActions() {
    return Container(
      height: 81,
      color: const Color(0xaa17161f),
      child: Row(
        children: [
          /// 关闭按钮
          SizedBox(
            width: 95,
            height: 81,
            child: Center(
              child: GestureDetector(
                onTap: () {
                  widget.completed?.call(false);
                },
                child: Image.asset(
                  "images/icon_close_white.webp",
                  width: 30,
                  height: 30,
                  scale: 3,
                  package: "koo_daxue_record_player",
                ),
              ),
            ),
          ),
          const VerticalDivider(
            thickness: 1,
            indent: 15,
            endIndent: 15,
            color: Colors.white,
          ),
          Row(
            children: _colorListWidget(),
          ),
          Expanded(child: Container()),

          /// 退一步按钮
          SizedBox(
            width: 66,
            height: 81,
            child: GestureDetector(
              onTap: () {
                setState(() {
                  if (lineIndex > 0) {
                    lineIndex--;
                    newPoints.remove(lineIndex);
                  }
                });
              },
              child: Center(
                  child: Image.asset(
                lineIndex == 0
                    ? "images/icon_undo.webp"
                    : "images/icon_undo_white.webp",
                width: 30,
                height: 30,
                scale: 3,
                package: "koo_daxue_record_player",
              )),
            ),
          ),

          /// 清除按钮
          SizedBox(
            width: 66,
            height: 81,
            child: Center(
                child: GestureDetector(
              onTap: () {
                setState(() {
                  lineIndex = 0;
                  newPoints.clear();
                });
              },
              child: Image.asset(
                lineIndex == 0
                    ? "images/icon_clear_doodle.webp"
                    : "images/icon_clear_doodle_white.webp",
                width: 30,
                height: 30,
                scale: 3,
                package: "koo_daxue_record_player",
              ),
            )),
          ),
          const VerticalDivider(
            thickness: 1,
            indent: 15,
            endIndent: 15,
            color: Colors.white,
          ),

          /// 确定按钮
          SizedBox(
            width: 85,
            height: 81,
            child: Center(
                child: GestureDetector(
              onTap: () {
                if (isSaved) return;
                isSaved = true;
                if (newPoints.isEmpty) {
                  widget.completed?.call(false);
                  return;
                }
                saveDoodle(widget.snapShotPath).then((value) {
                  if (value) {
                    widget.completed?.call(true);
                  } else {
                    widget.completed?.call(false);
                  }
                });
              },
              child: Image.asset(
                "images/icon_finish_white.webp",
                width: 30,
                height: 30,
                scale: 3,
                package: "koo_daxue_record_player",
              ),
            )),
          )
        ],
      ),
    );
  }

  List<Widget> _colorListWidget() {
    List<Widget> widgetList = [];
    for (int i = 0; i < colors.length; i++) {
      Color color = colors[i];
      widgetList.add(GestureDetector(
        onTap: () {
          setState(() {
            currentSelect = i;
          });
        },
        child: CircleRingWidget(i == currentSelect, color),
      ));
    }
    return widgetList;
  }

  Future<bool> saveDoodle(String imgPath) async {
    try {
      RenderRepaintBoundary boundary =
          globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
      ui.Image image = await boundary.toImage(pixelRatio: 3.0);
      ByteData? byteData =
          await image.toByteData(format: ui.ImageByteFormat.png);
      Uint8List pngBytes = byteData!.buffer.asUint8List();
      // 保存图片到文件
      File imgFile = File(imgPath);
      await imgFile.writeAsBytes(pngBytes);
      return true;
    } catch (e) {
      return false;
    }
  }
}

以上就是Flutter实现给图片添加涂鸦功能的详细内容,更多关于Flutter给图片添加涂鸦的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文