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>
    with TickerProviderStateMixin {
  // 定义一个裁剪
  CompareCustomClipper? compareCustomClipper;

  // late AnimationController _animateController =
  //     AnimationController(vsync: this, duration: Duration(milliseconds: 2500));
  // late Animation<double> _animation =
  //     CurvedAnimation(parent: _animateController, curve: Curves.linear);

  double offsetX = 0;
  double startOffsetX = 0;
  bool isStarted = false;

  // // 显示Tip
  // bool isShowTip = true;

  @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");
    if (isStarted == false) {
      startOffsetX = details.globalPosition.dx;
    }
    isStarted = true;
    print("onHorizontalDragDown startOffsetX:${startOffsetX}");
  }

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

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

    offsetX = widget.width! + distance;
    offsetX = offsetX.clamp(0, widget.width!);

    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://u3d-qiniu.dface.cn/Fsgjbe7O8Z5x83_Aff8-Qage9bpc.png",
              fit: BoxFit.cover,
              width: widget.width,
              height: widget.height,
            ),
          ),

          // 美颜之后的图
          ClipRect(
            clipper: compareCustomClipper,
            clipBehavior: Clip.hardEdge,
            child: CachedNetworkImage(
              imageUrl: "https://u3d-qiniu.dface.cn/64c2fba1-81ff-41dc-b32e-6920b0677f83",
              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开发实战-美颜前后对比图效果实现

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值