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

news2024/11/16 19:57:38

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开发实战-美颜前后对比图效果实现

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一张SSL证书如何同时保护多个域名及其子域名?

在互联网时代&#xff0c;数据安全和隐私保护变得至关重要&#xff0c;而SSL证书作为确保网站安全的重要工具&#xff0c;其重要性不言而喻。本文将详细探讨一种特殊的SSL证书——多域名通配符SSL证书&#xff0c;它为网站管理员提供了一种高效、经济的方式来保护多个域名及其子…

第一后裔加速器推荐 第一后裔免费加速器用哪个

知名游戏开发商NEXON对于许多老玩家来说都不会陌生&#xff0c;它旗下的泡泡堂和DNF可谓是一代人的青春。就在最近NEXON又为玩家们带来了最新作品《第一后裔》&#xff0c;该款游戏为搭建在虚幻5引擎上的一款多人联机射击掉宝类游戏&#xff0c;一上线就受到了许多游戏玩家的关…

高速公路定向广播(声光一体) HT-600D

1、产品概述&#xff1a; HT-600D声光一体平面波IP定向广播是北京恒星科通创新性研发产品&#xff0c;采用公司自主研发的平面波传声技术&#xff0c;该产品具有高声压、强指向性、高清晰度等特点&#xff0c;采用定向声传声技术将声音聚集到正前方定向传输,周边声压级明显降低…

【EXCEL_VBA_实战】两组数据比对是否一致(字符串数组)

工作背景&#xff1a;比对两组数据是否一致&#xff08;位置非一一对应&#xff09; 思路构建&#xff1a;两组数据转换为两组字符串数组&#xff0c;比对所包含元素是否相同 问题点&#xff1a;A数组的第一个元素不一定与B数组的第一个元素对应&#xff0c;此时无法通过公式…

数据仓库和数据挖掘基础

文章目录 1. 数据仓库基础知识1.1 数据仓库的基本特性1.2 数据仓库的数据模式1.3 数据仓库的体系结构 2. 数据挖掘基础知识2.1 数据挖掘的分类2.2 数据挖掘技术2.3 数据挖掘的应用过程 传统数据库在联机事务处理(OLTP)中获得了较大的成功&#xff0c;但是对管理人员的决策分析要…

SAP---成本中心采购跟消耗性采购的区别

1.常规库存采购业务的说明&#xff1a; 1.从业务层面分析&#xff0c;企业的常规库存物料采购是&#xff1a; 采购部门下采购订单后&#xff0c;供应商送货&#xff0c;当货物到厂后&#xff0c;由库管员执行收货操作&#xff0c;先将货物收到仓库中&#xff0c;再由各个需求…

tomcat jdbc连接池的默认配置配置方案

MySQL 5.0 以后针对超长时间数据库连接做了一个处理&#xff0c;即一个数据库连接在无任何操作情况下过了 8 个小时后(MySQL 服务器默认的超时时间是 8 小时)&#xff0c;MySQL 会自动把这个连接关闭。在数据库连接池中的 connections 如果空闲超过 8 小时&#xff0c;MySQL 将…

肌肤暗沉与胶原蛋白:解锁透亮肌肤的秘密

&#x1f338;亲爱的小仙女们&#xff0c;今天我们来聊聊肌肤暗沉与胶原蛋白之间的神秘联系。你是不是也曾为肌肤的暗沉而烦恼&#xff1f;其实&#xff0c;很多时候&#xff0c;肌肤的暗沉不仅仅是外部因素造成的&#xff0c;更与肌肤内部的胶原蛋白含量密切相关。&#x1f31…

element ui 下拉框Select 选择器 上下箭头旋转方向样式错乱——>优化方案

目录 前言1、问题复现2、预期效果3、input框样式修改解析4、修改方案 &#x1f680;写在最后 前言 测试A&#xff1a;那啥&#xff01;抠图仔&#xff0c;样式怎么点着点着就出问题了。 前端&#xff1a;啥&#xff1f;css样式错乱了&#xff1f;你是不是有缓存啊&#xff01…

智慧仓储新动力:EasyCVR+AI视频智能监管系统方案助力仓储安全高效管理

一、背景 随着物流行业的快速发展和智能化水平的提升&#xff0c;智慧仓储视频智能监管系统已成为现代仓储管理的重要组成部分。本系统通过综合运用物联网、视频分析、边缘计算等技术手段&#xff0c;实现对仓储环境的全面监控、智能分析和高效管理。 TSINGSEE青犀视频汇聚Ea…

李斌阻击马斯克,也不放过李想

市场唯一不变的就是变化。 当年特斯拉开放专利&#xff0c;引起了国内电动车的创业潮&#xff0c;蔚来比小鹏、理想早几个月成立&#xff0c;也是造车新势力中首家实现交付的品牌。 但时过境迁&#xff0c;现在已经不是蔚来领衔“蔚小理”的时代了&#xff0c;理想是其中销量…

Java对象的内存分配机制

下面以一段代码为示例&#xff1a; public class Person{int age;String name; } class Test{public static void main(String[]args){Person A new Person();A.age10;A.name"张三";System.out.println(A.age);System.out.println(A.name);} }

【话题】你眼中的IT行业现状与未来趋势

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读小5的系列文章&#xff0c;这是《话题》系列文章 目录 引言一、IT行业的现状1.1 云计算与大数据1.2 人工智能与机器学习1.3 物联网与5G通信1.4 区块链技术 二、IT行业未来发展趋势2.1 边缘计算与智能设备2.2 深度学习与自然语…

SBC3568启动升级,灵活更换动画logo

今天小智将会带着大家体验如何在openharmony sdk内替换开机logo和动态动画。 1. 更换开机logo 开机logo分为uboot阶段【logo.bmp】和kernel阶段【logo_kernel.bmp】的logo两个文件&#xff0c;对图片的要求是&#xff1a;必须为bmp格式&#xff0c;8或者24位深&#xff0c;且…

小程序-收货地址管理模块实现

页面结构代码&#xff1a; address-form.vue --->新建地址和修改地址页面 <template><view class"content"><form><!-- 表单内容 --><view class"form-item"><text class"label">收货人</text>…

青少年 CTF 练习平台:Misc(一)

前言 当然&#xff0c;我可以更详细地介绍一下青少年CTF练习平台。 青少年CTF练习平台是一个专为青少年设计的网络安全竞赛和训练平台。该平台由思而听&#xff08;山东&#xff09;网络科技有限公司与克拉玛依市思而听网络科技有限公司共同建设&#xff0c;自2018年创建以来…

IDEA连接MySQL后如何管理数据库

上一节讲解了IDEA如何连接MySQL数据库管理系统&#xff0c;接下来我们就可以在IDEA里使用MySQL来管理数据库了。那么如果我们现在还没有创建需要的数据库怎么办&#xff1f;本节就来教大家如何在IDEA连接MySQL后管理数据库(创建/修改/删除数据库、创建/修改/删除表、插入/更新/…

ICML2024高分论文!大模型计算效率暴涨至200%,来自中国AI公司

前段时间&#xff0c;KAN突然爆火&#xff0c;成为可以替代MLP的一种全新神经网络架构&#xff0c;200个参数顶30万参数&#xff1b;而且&#xff0c;GPT-4o的生成速度也是惊艳了一众大模型爱好者。 大家开始意识到—— 大模型的计算效率很重要&#xff0c;提升大模型的token…

【linux-uboot移植-mmc及tftp启动-IMX6ULL】

目录 1. uboot简介2. 移植前的基本介绍&#xff1a;2.1 环境系统信息: 3. 初次编译4. 烧录编译的u-boot4.1 修改网络驱动 5. 通过命令启动linux内核5.1 通过命令手动启动mmc中的linux内核5.1.1 fatls mmc 1:15.1.2 fatload mmc 1:1 0x80800000 zImage5.1.3 fatload mmc 1:1 0x8…

力扣HOT100 - 169. 多数元素

解题思路&#xff1a; 有点类似于Boyer-Moore 投票算法&#xff0c;但更加形象。 class Solution {public int majorityElement(int[] nums) {int winner nums[0];int cnt 1;for (int i 1; i < nums.length; i) {if (winner nums[i]){cnt;} else if (cn…