flutter开发实战-美颜前后对比图效果实现

flutter开发实战-美颜前后对比图效果实现
在这里插入图片描述

最近使用代码中遇到了图片前后对比,这里使用的是CustomClipper来实现

一、CustomClipper

我们实现CustomClipper子类来实现美颜后的图片裁剪功能

getClip()是用于获取剪裁区域的接口,由于图片大小是60×60,
我们返回剪裁区域为Rect.fromLTWH(10.0, 15.0, 40.0, 30.0),即图片中部40×30像素的范围。
shouldReclip() 接口决定是否重新剪裁。
如果在应用中,剪裁区域始终不会发生变化时应该返回false,这样就不会触发重新剪裁,避免不必要的性能开销。
如果剪裁区域会发生变化(比如在对剪裁区域执行一个动画),那么变化后应该返回true来重新执行剪裁。

二、使用CustomClipper来实现美颜前后对比图效果

美颜前后对比图,原图展示,美颜后的图片根据手势拖动距离进行裁剪
美颜之后的图裁剪,设置Clip.hardEdge。

ClipRect(
            clipper: compareCustomClipper,
            clipBehavior: Clip.hardEdge,
            child: CachedNetworkImage(
              imageUrl: "https://qiniu.example.com/64c2fba1-81ff-41dc-b32e-6920b0677f8c0",
              fit: BoxFit.cover,
              width: widget.width,
              height: widget.height,
            ),
          ),
    

手势拖动,更新compareCustomClipper

void onHorizontalDragDown(DragDownDetails details) {
    print("onHorizontalDragDown");
    startOffsetX = details.localPosition.dx;
    print("onHorizontalDragDown startOffsetX:${startOffsetX}");
  }

  void onHorizontalDragStart(DragStartDetails details) {
    print("onHorizontalDragStart");
  }

  void onHorizontalDragUpdate(DragUpdateDetails details) {
    print("onHorizontalDragUpdate");
    double curOffsetX = details.localPosition.dx;
    double distance = curOffsetX - startOffsetX;
    print("onHorizontalDragUpdate curOffsetX:${curOffsetX}, startOffsetX:${startOffsetX}, distance:${distance}");

    offsetX = widget.width! + distance;
    if (offsetX > widget.width!) {
      offsetX = widget.width!;
    }

    if (offsetX < 0) {
      offsetX = 0;
    }

    compareCustomClipper = CompareCustomClipper(Rect.fromLTWH(
        offsetX, 0.0, (widget.width ?? 0) - offsetX, widget.height ?? 0));
    setState(() {});
  }
    

完整代码如下


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

class ComparePicPage extends StatefulWidget {
  const ComparePicPage({super.key});

  @override
  State<ComparePicPage> createState() => _ComparePicPageState();
}

class _ComparePicPageState extends State<ComparePicPage> {
  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('ComparePicPage'),
      ),
      body: Center(
        child: ComparePicWidget(
          width: 320,
          height: 480,
        ),
      ),
    );
  }
}

// 自定义裁剪CustomClipper
class CompareCustomClipper extends CustomClipper<Rect> {
  CompareCustomClipper(this.rect);

  Rect rect;

  // Rect getClip(Size size) => Rect.fromLTWH(0.0, 15.0, 40.0, 30.0);

  @override
  Rect getClip(Size size) => rect;

  @override
  bool shouldReclip(CustomClipper<Rect> oldClipper) => true;
}

/// 图片美颜前后对比
class ComparePicWidget extends StatefulWidget {
  const ComparePicWidget({
    super.key,
    this.width,
    this.height,
  });

  final double? width;
  final double? height;

  @override
  State<ComparePicWidget> createState() => _ComparePicWidgetState();
}

class _ComparePicWidgetState extends State<ComparePicWidget> {
  // 定义一个裁剪
  CompareCustomClipper? compareCustomClipper;

  double offsetX = 0;
  double startOffsetX = 0;


  @override
  void initState() {
    // TODO: implement initState
    offsetX = widget.width ?? 0;
    compareCustomClipper = CompareCustomClipper(Rect.fromLTWH(
        offsetX, 0.0, (widget.width ?? 0) - offsetX, widget.height ?? 0));
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  void onHorizontalDragDown(DragDownDetails details) {
    print("onHorizontalDragDown");
    startOffsetX = details.localPosition.dx;
    print("onHorizontalDragDown startOffsetX:${startOffsetX}");
  }

  void onHorizontalDragStart(DragStartDetails details) {
    print("onHorizontalDragStart");
  }

  void onHorizontalDragUpdate(DragUpdateDetails details) {
    print("onHorizontalDragUpdate");
    double curOffsetX = details.localPosition.dx;
    double distance = curOffsetX - startOffsetX;
    print("onHorizontalDragUpdate curOffsetX:${curOffsetX}, startOffsetX:${startOffsetX}, distance:${distance}");

    offsetX = widget.width! + distance;
    if (offsetX > widget.width!) {
      offsetX = widget.width!;
    }

    if (offsetX < 0) {
      offsetX = 0;
    }

    compareCustomClipper = CompareCustomClipper(Rect.fromLTWH(
        offsetX, 0.0, (widget.width ?? 0) - offsetX, widget.height ?? 0));
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: widget.width,
      height: widget.height,
      decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: const BorderRadius.all(
          Radius.circular(10),
        ),
        border: Border.all(
          color: Colors.transparent,
          width: 0,
          style: BorderStyle.solid,
        ),
      ),
      clipBehavior: Clip.none,
      child: Stack(
        alignment: Alignment.center,
        clipBehavior: Clip.none,
        children: [
          // 原图
          ClipRect(
            clipBehavior: Clip.hardEdge,
            child: CachedNetworkImage(
              imageUrl: "https://qiniu.example.com/Fsgjbe7O8Z5x83_Aff8-Qage9bpc8e.png",
              fit: BoxFit.cover,
              width: widget.width,
              height: widget.height,
            ),
          ),

          // 美颜之后的图
          ClipRect(
            clipper: compareCustomClipper,
            clipBehavior: Clip.hardEdge,
            child: CachedNetworkImage(
              imageUrl: "https://qiniu.example.com/64c2fba1-81ff-41dc-b32e-6920b0677f833c",
              fit: BoxFit.cover,
              width: widget.width,
              height: widget.height,
            ),
          ),
          // line
          Positioned(
            left: offsetX + 26.5 + (-27.5),
            child: buildLine(context),
          ),
          Positioned(
            left: offsetX + (-27.5),
            child: buildCustomButton(context),
          ),
          // tip
          Positioned(
            left: 20,
            top: 20,
            child: buildCompareTip(context, "原图"),
          ),
          Positioned(
            right: 20,
            top: 20,
            child: buildCompareTip(context, "美颜后"),
          ),
        ],
      ),
    );
  }

  Widget buildLine(BuildContext context) {
    return Image.asset(
      "assets/images/line.png",
      width: 2,
      height: 576,
      fit: BoxFit.cover,
    );
  }

  Widget buildCustomButton(BuildContext context) {
    return GestureDetector(
      onHorizontalDragDown: (DragDownDetails details) {
        onHorizontalDragDown(details);
      },
      onHorizontalDragStart: (DragStartDetails details) {
        onHorizontalDragStart(details);
      },
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        onHorizontalDragUpdate(details);
      },
      child: Image.asset(
        "assets/images/move_button.png",
        width: 55,
        height: 55,
        fit: BoxFit.cover,
      ),
    );
  }

  Widget buildCompareTip(BuildContext context, String title) {
    return Container(
      width: 60,
      height: 30,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        color: Colors.black.withOpacity(0.35),
        borderRadius: const BorderRadius.all(
          Radius.circular(20),
        ),
      ),
      child: Text(
        title,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
        textAlign: TextAlign.center,
        style: const TextStyle(
          fontSize: 13,
          fontWeight: FontWeight.w600,
          fontStyle: FontStyle.normal,
          color: Colors.white,
          decoration: TextDecoration.none,
        ),
      ),
    );
  }
}

三、小结

flutter开发实战-美颜前后对比图效果实现

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/640705.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

刷代码随想录有感(76):回溯算法——全排列

题干&#xff1a; 代码&#xff1a; class Solution { public:vector<int> tmp;vector<vector<int>> res;void backtracking(vector<int> nums, vector<int> used){if(tmp.size() nums.size()){res.push_back(tmp);return;}for(int i 0; i &l…

生活小区火灾预警新篇章:泵吸式可燃气体报警器的检定与运用

在现代化的生活小区中&#xff0c;燃气设备广泛应用于居民的日常生活之中&#xff0c;但同时也带来了潜在的火灾风险。 可燃气体报警器作为一种安全监测设备&#xff0c;能够及时检测到燃气泄漏等安全隐患&#xff0c;并在达到预设的阈值时发出警报&#xff0c;提醒居民采取相…

一个基于HOOK机制的微信机器人

一个基于✨HOOK机制的微信机器人&#xff0c;支持&#x1f331;安全新闻定时推送【FreeBuf&#xff0c;先知&#xff0c;安全客&#xff0c;奇安信攻防社区】&#xff0c;&#x1f46f;Kfc文案&#xff0c;⚡备案查询&#xff0c;⚡手机号归属地查询&#xff0c;⚡WHOIS信息查询…

DDD领域设计在“图生代码”中的应用实践

前言 领域驱动设计(简称 ddd)概念来源于2004年著名建模专家Eric Evans 发表的他最具影响力的书籍:《领域驱动设计——软件核心复杂性应对之道》&#xff08;Domain-Driven Design –Tackling Complexity in the Heart of Software&#xff09;&#xff0c;简称Evans DDD。领域…

linux命令中arpd的使用

arpd 收集免费ARP信息 补充说明 arpd命令 是用来收集免费arp信息的一个守护进程&#xff0c;它将收集到的信息保存在磁盘上或者在需要时&#xff0c;提供给内核用户用于避免多余广播。 语法 arpd(选项)(参数)选项 -l&#xff1a;将arp数据库输出到标准输出设备显示并退出…

BL121DT网关在智能电网分布式能源管理中的应用钡铼技术协议网关

随着全球能源结构的转型和智能电网技术的飞速发展&#xff0c;分布式能源管理系统在提高能源利用效率、促进可再生能源接入及保障电网稳定运行方面发挥着日益重要的作用。然而&#xff0c;分布式能源系统内设备种类繁多&#xff0c;通信协议各异&#xff0c;如何高效整合这些设…

SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?

尼恩&#xff1a;LLM大模型学习圣经PDF的起源 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;经常性的指导小伙伴们改造简历。 经过尼恩的改造之后&#xff0c;很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会&#x…

拼多多携手中国农业大学,投建陕西佛坪山茱萸科技小院

5月16日下午&#xff0c;中国农业大学陕西佛坪山茱萸科技小院在佛坪县银厂沟村揭牌。佛坪县素有“中国山茱萸之乡”的美誉&#xff0c;是全国山茱萸三大基地之一&#xff0c;当地山茱萸是国家地理标志产品&#xff0c;山茱萸肉产量位居全国第二。 为充分发挥佛坪县得天独厚的山…

[猫头虎分享21天微信小程序基础入门教程] 第11天:小程序的动态数据展示与实时更新

[猫头虎分享21天微信小程序基础入门教程] 第11天&#xff1a;小程序的动态数据展示与实时更新 — 第11天&#xff1a;小程序的动态数据展示与实时更新 &#x1f4ca; 自我介绍 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师。今天我们继续微信小程序的学习&a…

49页 | 2024年人工智能内生安全白皮书(免费下载)

以上是资料简介和目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a;

jmeter之线程组教程

一、线程组的作用 线程组是测试计划的子控件&#xff0c;也是取样器的父控件setup线程组&#xff0c;在所有线程组之前运行&#xff0c;类似于unittest框架中的SetUp函数&#xff0c;初始化环境teardown线程组&#xff0c;在所有线程组之后运行&#xff0c;类似于unittest中的…

【学习笔记】Windows GDI绘图目录

题外话 不知几时开始&#xff0c;觉得学习过程中将内容记录下来&#xff0c;有助于加强记忆&#xff0c;还方便后续查找&#xff0c;顺便帮助有需要的人&#xff0c;更有来自您阅读、点赞、收藏和评论时给我带来的动力与兴奋。 目录 【学习笔记】Windows GDI绘图(一)图形概述…

机器学习之支持向量机SVM

支持向量机 概念 是supported vector machine&#xff08;支持向量机&#xff09;&#xff0c;即寻找一个超平面使样本分成两类&#xff0c;且间隔最大分类 分类 硬间隔 若样本线性可分&#xff0c;且所有样本分类正确情况下&#xff0c;寻找最大间隔&#xff0c;即硬间隔 若…

SpringBoot Redis 扩展高级功能

环境&#xff1a;SpringBoot2.7.16 Redis6.2.1 1. Redis消息发布订阅 Spring Data 为 Redis 提供了专用的消息传递集成&#xff0c;其功能和命名与 Spring Framework 中的 JMS 集成类似。Redis 消息传递大致可分为两个功能区域&#xff1a; 信息发布 信息订阅 这是一个通常…

有哪些地图采集软件可以采集商家数据导出功能?

1.国内商家采集 寅甲地图数据采集软件 寅甲地图数据采集软件一款多关键词多城市同时采集百度地图、360地图、高德地图、搜狗地图、腾讯地图、图吧地图、天地图商家、公司、店铺的手机、座机、地址、坐标等数据信息的软件。 2.国外商家采集 寅甲谷歌地图数据采集软件 专为做…

Java学习路线思维导图

目录 Java学习流程1.学习大纲2.Java开发中常用的DOS命令 Java入门学习思维导图 Java学习流程 通过大纲了解学习的重点&#xff0c;通过目录依次深入【注&#xff1a;Java环境的搭建百度&#xff0c;提升自己百度的能力】 1.学习大纲 学习流程如下&#xff1a; Java基础语法 …

操作系统3_作业与处理机调度

操作系统3_作业与处理机调度 文章目录 操作系统3_作业与处理机调度1. 作业的概念与组成2. 作业的建立及状态3. 处理机调度相关概念3.1 调度级别3.2 调度队列模型3.3 选择准则4. 作业调度与进程调度5. 典型处理机调度算法5.1 先来先服务算法FCFS5.2 短作业优先算法SJF5.3 优先级…

Python中别再用 ‘+‘ 拼接字符串了!

大家好&#xff0c;在 Python 编程中&#xff0c;我们常常需要对字符串进行拼接。你可能会自然地想到用 操作符将字符串连接起来&#xff0c;毕竟这看起来简单明了。 在 Python 中&#xff0c;字符串是不可变的数据类型&#xff0c;这意味着一旦字符串被创建&#xff0c;它就…

HarmonyOS 鸿蒙应用开发 DevEco Studio环境搭建 (值得收藏哦)

目录 1、华为开发者官网下载 DevEco Studio 2、安装DevEco Studi 3、安装过程具体步骤 4、认证华为开发者账号 5、编写第一个鸿蒙应用 1、华为开发者官网下载 DevEco Studio 前往&#xff1a;华为开发者官网地址 下载&#xff0c;我这里下载的 deveco-studio-3.1.0.500版…

VSCode SAP Systems配置HTTPS访问SAP

第一次访问提示&#xff0c;Self-Signed 证书 解决办法&#xff1a;https访问SAP Fiori网站&#xff0c;导出SSL证书为DER格式保存到硬盘上 双击DER文件&#xff0c;导入到系统 退出VSCode&#xff0c;再次启动 Test Connection, 提示 The system URL is using a hostname …