欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter實(shí)現(xiàn)給圖片添加涂鴉功能

 更新時(shí)間:2024年01月25日 08:23:15   作者:如此風(fēng)景  
這篇文章主要介紹了利用Flutter實(shí)現(xiàn)給圖片添加涂鴉功能,文中通過代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

簡(jiǎn)介

先來張圖,看一下最終效果

關(guān)閉和確定

  • 關(guān)閉和確定功能對(duì)應(yīng)的是界面左下角叉號(hào),和右下角對(duì)鉤,關(guān)閉按鈕僅做取消當(dāng)次涂鴉,讀者可自行設(shè)置點(diǎn)擊后功能,也可自行更改相應(yīng)UI。選擇功能點(diǎn)擊后會(huì)執(zhí)行一段把當(dāng)前涂鴉后的圖片合成并保存到本地的操作。具體請(qǐng)看示例代碼。

顏色選擇

  • 顏色選擇功能可選擇和標(biāo)識(shí)當(dāng)前涂鴉顏色,和指示當(dāng)前涂鴉顏色的選中狀態(tài)(以白色外圈標(biāo)識(shí))。切換顏色后下一次涂鴉即會(huì)使用新的顏色。

撤銷功能

  • 撤銷功能可撤銷最近的一次涂鴉。如沒有涂鴉時(shí)顯示置灰的撤銷按鈕。

清除功能

  • 清除功能可清除所有涂鴉,如當(dāng)前沒有任何涂鴉時(shí)顯示置灰的清除按鈕。

涂鴉圖片的放大和縮小

  • 可雙指滑動(dòng)切換涂鴉放大縮小的效果。

放大縮小后按照新的線條粗細(xì)繼續(xù)涂鴉

  • 涂鴉放大或縮小后,涂鴉線條會(huì)隨之放大和縮小,此時(shí)如果繼續(xù)涂鴉,則新涂鴉顯示的粗細(xì)程度與放大或縮小后的線條粗細(xì)程度保持一致。

保存涂鴉圖片到本地。

  • flutter涂鴉后的圖片可合成新圖片并保存到本地路徑。

代碼介紹

涂鴉顏色選擇組件。

主要是顯示為可配置的圓點(diǎn)和外圈

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), // 調(diào)整尺寸大小
    );
  }
}

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 // 設(shè)置圓點(diǎn)的顏色
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 1.0;

    Paint ringPaint = Paint()
      ..color = Colors.white // 設(shè)置圓環(huán)的顏色
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 1.0
      ..style = PaintingStyle.stroke;

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

    // 畫一個(gè)半徑為10的圓點(diǎn)
    canvas.drawCircle(center, 13.0, circlePaint);

    if(isShowRing){
      // 畫一個(gè)半徑為20的圓環(huán)
      canvas.drawCircle(center, 18.0, ringPaint);
    }
  }

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

存儲(chǔ)顏色和食指劃過點(diǎn)的數(shù)據(jù)類

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);
  }
}

具體涂鴉點(diǎn)的繪制

此處是具體繪制涂鴉點(diǎn)的自定義view。大家是不是覺得哇,好簡(jiǎn)單呢。兩個(gè)循環(huán)一嵌套,瞬間所有涂鴉就都出來了。其實(shí)做這個(gè)功能時(shí),我參考了其他各種涂鴉控件,但是總覺得流程非常復(fù)雜。難以理解。原因是他們的顏色和點(diǎn)在數(shù)據(jù)層面都是混合到一起的,而且還得判斷哪里是新畫的涂鴉線條,來回控制。用這個(gè)demo的結(jié)構(gòu),相信各位讀者一看就能知道里面的思路

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++){
        //最后一個(gè)畫點(diǎn),其他畫線
        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;
  }
}

涂鴉主要界面代碼

  • 包含整體涂鴉數(shù)據(jù)的構(gòu)建
  • 包含涂鴉圖片的合成和本地存儲(chǔ)
  • 包含涂鴉顏色列表的自定義
  • 包含涂鴉原圖片的放大縮小
  • 包含撤銷一步和清屏功能

下面這些就是整體涂鴉相關(guān)功能代碼,其中一些資源圖片未提供,請(qǐng)根據(jù)需要自己去設(shè)計(jì)處獲取。

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(() {
      ///獲取矩陣?yán)锩娴目s放具體值
      realScale = controller.value.entry(0, 0);

      ///獲取矩陣?yán)锩娴奈恢闷屏?
      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執(zhí)行了  dx=${details.focalPoint.dx} dy=${details.focalPoint.dy}");
            },
            onInteractionUpdate: (ScaleUpdateDetails details) {
              if (details.pointerCount == 1) {
                /// 獲取 x,y 拿到值后進(jìn)行縮放偏移等換算
                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執(zhí)行了");
              if (points.length > 5) {
                newPoints[lineIndex] =
                    ColorOffset(colors[currentSelect], points);
                lineIndex++;
              }
              // newPoints.addAll({lineIndex:ColorOffset(colors[currentSelect],points)});
              //清空原數(shù)組
              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: [
          /// 關(guān)閉按鈕
          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實(shí)現(xiàn)給圖片添加涂鴉功能的詳細(xì)內(nèi)容,更多關(guān)于Flutter給圖片添加涂鴉的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • AndroidStudio工程打包aab文件

    AndroidStudio工程打包aab文件

    本文主要介紹了AndroidStudio工程打包aab文件,文中通過圖文的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • Kotlin如何使用類似C#的yield功能詳解

    Kotlin如何使用類似C#的yield功能詳解

    在語句中使用 yield 關(guān)鍵字,則指示在的方案、運(yùn)算符或 get 訪問器是迭代器。下面這篇文章主要給大家介紹了關(guān)于Kotlin如何使用類似C#的yield功能的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧
    2018-06-06
  • Android異步消息機(jī)制詳解

    Android異步消息機(jī)制詳解

    這篇文章主要為大家詳細(xì)介紹了Android異步消息機(jī)制的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android實(shí)現(xiàn)文件按時(shí)間先后順序排列顯示的示例代碼

    Android實(shí)現(xiàn)文件按時(shí)間先后順序排列顯示的示例代碼

    在很多Android應(yīng)用中,需要管理和展示本地文件,對(duì)文件按最后修改時(shí)間排序展示,能讓用戶直觀地了解文件的創(chuàng)建或修改順序,從而更方便地查找最新或最舊的文件,本文將介紹如何在Android平臺(tái)上獲取指定目錄下的文件列表,并按照時(shí)間先后排序,需要的朋友可以參考下
    2025-04-04
  • Flutter開發(fā)技巧RadialGradient中radius計(jì)算詳解

    Flutter開發(fā)技巧RadialGradient中radius計(jì)算詳解

    這篇文章主要為大家介紹了Flutter小技巧RadialGradient?中?radius?的計(jì)算詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Android自定義 WebView瀏覽器

    Android自定義 WebView瀏覽器

    WebView是Android中一個(gè)非常實(shí)用的組件,它和Safai、Chrome一樣都是基于Webkit網(wǎng)頁(yè)渲染引擎。接下來通過本文給大家介紹android自定義webview瀏覽器,感興趣的朋友一起學(xué)習(xí)吧
    2016-05-05
  • 深入分析Android ViewStub的應(yīng)用詳解

    深入分析Android ViewStub的應(yīng)用詳解

    本篇文章是對(duì)Android ViewStub的應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • android實(shí)現(xiàn)session保持簡(jiǎn)要概述及實(shí)現(xiàn)

    android實(shí)現(xiàn)session保持簡(jiǎn)要概述及實(shí)現(xiàn)

    其實(shí)sesion在瀏覽器和web服務(wù)器直接是通過一個(gè)叫做name為sessionid的cookie來傳遞的,所以只要在每次數(shù)據(jù)請(qǐng)求時(shí)保持sessionid是同一個(gè)不變就可以用到web的session了,感興趣的你可以參考下本文或許對(duì)你有所幫助
    2013-03-03
  • Android Handler多線程詳解

    Android Handler多線程詳解

    本文主要介紹Android Handler的知識(shí),這里整理了詳細(xì)的資料及簡(jiǎn)單示例代碼和實(shí)現(xiàn)效果圖,有興趣的小伙伴可以參考下
    2016-09-09
  • Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法

    Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法

    這篇文章主要為大家詳細(xì)介紹了Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06

最新評(píng)論