{"id":13789555,"url":"https://github.com/fluttercandies/extended_image","last_synced_at":"2025-05-13T19:17:29.332Z","repository":{"id":37336166,"uuid":"172701007","full_name":"fluttercandies/extended_image","owner":"fluttercandies","description":"A powerful official extension library of image, which support placeholder(loading)/ failed state, cache network, zoom pan image, photo view, slide out page, editor(crop,rotate,flip), paint custom etc.","archived":false,"fork":false,"pushed_at":"2025-04-21T09:17:30.000Z","size":30023,"stargazers_count":1993,"open_issues_count":36,"forks_count":510,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-04-28T10:57:48.256Z","etag":null,"topics":["cache","crop","flip","flutter","image","rotate","zoom"],"latest_commit_sha":null,"homepage":"https://fluttercandies.github.io/extended_image/","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fluttercandies.png","metadata":{"files":{"readme":"README-ZH.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"custom":"http://zmtzawqlp.gitee.io/my_images/images/qrcode.png"}},"created_at":"2019-02-26T11:45:24.000Z","updated_at":"2025-04-28T09:31:50.000Z","dependencies_parsed_at":"2023-02-06T08:15:31.777Z","dependency_job_id":"89e7d343-cdcc-4396-b06b-bc4bc033ec84","html_url":"https://github.com/fluttercandies/extended_image","commit_stats":{"total_commits":297,"total_committers":20,"mean_commits":14.85,"dds":"0.11111111111111116","last_synced_commit":"618a0f06f145db3cf2b6db8712cee5e472936843"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluttercandies%2Fextended_image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluttercandies%2Fextended_image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluttercandies%2Fextended_image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluttercandies%2Fextended_image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluttercandies","download_url":"https://codeload.github.com/fluttercandies/extended_image/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254010830,"owners_count":21999004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","crop","flip","flutter","image","rotate","zoom"],"created_at":"2024-08-03T22:00:27.863Z","updated_at":"2025-05-13T19:17:29.268Z","avatar_url":"https://github.com/fluttercandies.png","language":"Dart","readme":"# extended_image\n\n[![pub package](https://img.shields.io/pub/v/extended_image.svg)](https://pub.dartlang.org/packages/extended_image) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_image)](https://github.com/fluttercandies/extended_image/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_image)](https://github.com/fluttercandies/extended_image/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_image)](https://github.com/fluttercandies/extended_image/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_image)](https://github.com/fluttercandies/extended_image/issues) \u003ca href=\"https://qm.qq.com/q/ZyJbSVjfSU\"\u003e![FlutterCandies QQ 群](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffluttercandies%2F.github%2Frefs%2Fheads%2Fmain%2Fdata.yml\u0026query=%24.qq_group_number\u0026label=QQ%E7%BE%A4\u0026logo=qq\u0026color=1DACE8)\n\n文档语言: [English](README.md) | 中文简体\n\n强大的官方 Image 扩展组件, 支持加载以及失败显示，缓存网络图片，缩放拖拽图片，图片浏览(微信掘金效果)，滑动退出页面(微信掘金效果)，编辑图片(裁剪旋转翻转)，保存，绘制自定义效果等功能\n\n[Web demo for ExtendedImage](https://fluttercandies.github.io/extended_image/)\n\n* [Flutter 什么功能都有的Image](https://juejin.cn/post/6844903794656952328)\n* [Flutter 可以缩放拖拽的图片](https://juejin.cn/post/6844903814324027400)\n* [Flutter 仿掘金微信图片滑动退出页面效果](https://juejin.cn/post/6844903860163575815)\n* [Flutter 图片裁剪旋转翻转编辑器](https://juejin.cn/post/6844903939670802446)\n* [Flutter 图片全家桶](https://juejin.cn/post/6844904122571816968)\n* [Flutter 什么，微信现在才支持实况图！？](https://juejin.cn/post/7418391732162854927)\n\nExtendedImage 是官方 Image 的扩展三方库，主要扩展功能如下:\n\n| 功能                                           | ExtendedImage                                                                  | Flutter 官方 Image          |\n| ---------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------- |\n| 缓存网络图片资源本地以及从本地加载网络缓存资源 | 支持                                                                           | 不支持                      |\n| 压缩显示                                       | 支持，在官方的基础上，更多的灵活选项 compressionRatio 和 maxBytes 进行压缩显示 | 支持 cacheHeight,cacheWidth |\n| 自动释放图片资源                               | 支持                                                                           | 需手动管理图片资源          |\n| 缩放模式                                       | 支持                                                                           | 不支持                      |\n| 编辑模式                                       | 支持                                                                           | 不支持                      |\n| 拖动图片退出页面效果                           | 支持                                                                           | 不支持                      |\n\n\n\n## 目录\n\n- [extended\\_image](#extended_image)\n  - [目录](#目录)\n  - [导入](#导入)\n  - [缓存网络图片](#缓存网络图片)\n    - [简单使用](#简单使用)\n    - [使用 ExtendedNetworkImageProvider](#使用-extendednetworkimageprovider)\n  - [加载状态](#加载状态)\n    - [例子](#例子)\n  - [缩放拖拽](#缩放拖拽)\n    - [双击图片动画](#双击图片动画)\n  - [图片编辑](#图片编辑)\n    - [裁剪框的宽高比](#裁剪框的宽高比)\n    - [裁剪图层 Painter](#裁剪图层-painter)\n    - [翻转、旋转、重新设置裁剪比例、撤消、重做、重置](#翻转旋转重新设置裁剪比例撤消重做重置)\n      - [翻转](#翻转)\n      - [旋转](#旋转)\n      - [重新设置裁剪比例](#重新设置裁剪比例)\n      - [撤消](#撤消)\n      - [重做](#重做)\n      - [重置](#重置)\n      - [历史](#历史)\n      - [配置更新](#配置更新)\n    - [裁剪数据](#裁剪数据)\n      - [使用 dart 库(稳定)](#使用-dart-库稳定)\n      - [使用原生库(快速)](#使用原生库快速)\n  - [图片浏览](#图片浏览)\n  - [滑动退出页面](#滑动退出页面)\n    - [首先开启滑动退出页面效果](#首先开启滑动退出页面效果)\n    - [把你的页面用 ExtendedImageSlidePage 包一下](#把你的页面用-extendedimageslidepage-包一下)\n    - [确保你的页面是透明背景的](#确保你的页面是透明背景的)\n    - [Push 一个透明的页面](#push-一个透明的页面)\n  - [Border BorderRadius Shape](#border-borderradius-shape)\n  - [清除缓存和保存](#清除缓存和保存)\n    - [清除缓存](#清除缓存)\n    - [保存网络图片](#保存网络图片)\n  - [显示裁剪图片](#显示裁剪图片)\n  - [绘制](#绘制)\n  - [Notch](#notch)\n  - [内存使用](#内存使用)\n  - [其他 APIs](#其他-apis)\n\n\n## 导入\n\n*  空安全\n\n``` yaml\nenvironment:\n  sdk: '\u003e=2.12.0 \u003c3.0.0'\n  flutter: '\u003e=2.0'\ndependencies:\n  extended_image: ^4.0.0\n```\n\n*  非空安全\n\n1.22.6 到 2.0, Flutter Api 有 breaking change，所以 1.22.6 以下的，请使用非空安全版本\n\n``` yaml\nenvironment:\n  sdk: '\u003e=2.6.0 \u003c2.12.0'\n  flutter: '\u003e1.17.0 \u003c=1.22.6'\ndependencies:\n  extended_image: ^3.0.0-non-null-safety\n```\n\n## 缓存网络图片\n\n### 简单使用\n\n你可以直接使用 ExtendedImage.network，这跟官方是一样。\n\n```dart\nExtendedImage.network(\n  url,\n  width: ScreenUtil.instance.setWidth(400),\n  height: ScreenUtil.instance.setWidth(400),\n  fit: BoxFit.fill,\n  cache: true,\n  border: Border.all(color: Colors.red, width: 1.0),\n  shape: boxShape,\n  borderRadius: BorderRadius.all(Radius.circular(30.0)),\n  //cancelToken: cancellationToken,\n)\n```\n\n### 使用 ExtendedNetworkImageProvider\n\n你也可以通过[ExtendedNetworkImageProvider](https://github.com/fluttercandies/extended_image_library/blob/master/lib/src/extended_network_image_provider.dart)，设置更多的网络请求的参数\n\n```dart\nExtendedNetworkImageProvider(\n  this.url, {\n  this.scale = 1.0,\n  this.headers,\n  this.cache: false,\n  this.retries = 3,\n  this.timeLimit,\n  this.timeRetry = const Duration(milliseconds: 100),\n  CancellationToken cancelToken,\n})  : assert(url != null),\n      assert(scale != null),\n      cancelToken = cancelToken ?? CancellationToken();\n```\n\n| 参数        | 描述                  | 默认                |\n| ----------- | --------------------- | ------------------- |\n| url         | 网络请求地址          | required            |\n| scale       | ImageInfo 中的 scale  | 1.0                 |\n| headers     | HttpClient 的 headers | -                   |\n| cache       | 是否缓存到本地        | false               |\n| retries     | 请求尝试次数          | 3                   |\n| timeLimit   | 请求超时              | -                   |\n| timeRetry   | 请求重试间隔          | milliseconds: 100   |\n| cancelToken | 用于取消请求的 Token  | CancellationToken() |\n\n当然你也可以继承任何的 ExtendedProvider,并且覆写 instantiateImageCodec 方法，这样你可以统一处理图片的元数据，比如进行压缩图片。\n\n## 加载状态\n\nExtended Image 一共有 3 种状态，分别是正在加载，完成，失败(loading,completed,failed)，你可以通过实现 loadStateChanged 回调来定义显示的效果\n\nloadStateChanged 不仅仅只在网络图片中可以使用, 如果你的图片很大，需要长时间加载，\n你可以把 enableLoadState 设置为了 true，这样也会有状态回调了，（默认只有网络图片,enableLoadState 为 true）\n\n![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_image/custom.gif)\n\n注意:\n\n- 如果你不想重写某个状态，那么请返回 null\n\n- 如果你想重写完成图片的 size 或者 sourceRect, 你可以通过使用 ExtendedRawImage 来完成\n\n- 如果你想增加一些新效果 (比如动画), 你可以重写并且使用 ExtendedImageState.completedWidget\n\n- ExtendedImageState.completedWidget 包含手势或者裁剪, 这样你不会丢失它们\n\n```dart\n/// custom load state widget if you want\n    final LoadStateChanged loadStateChanged;\n\nenum LoadState {\n  //loading\n  loading,\n  //completed\n  completed,\n  //failed\n  failed\n}\n\n  ///whether has loading or failed state\n  ///default is false\n  ///but network image is true\n  ///better to set it's true when your image is big and take some time to ready\n  final bool enableLoadState;\n```\n\nExtendedImageState 状态回调\n\n| 参数/方法                    | 描述                                                                                                   | 默认 |\n| ---------------------------- | ------------------------------------------------------------------------------------------------------ | ---- |\n| extendedImageInfo            | 图片的信息，包括底层 image 和 image 的大小                                                             | -    |\n| extendedImageLoadState       | 状态(loading,completed,failed)                                                                         | -    |\n| returnLoadStateChangedWidget | 如果这个为 true 的话，状态回调返回的 widget 将不会对(width/height/gesture/border/shape）等效果进行包装 | -    |\n| imageProvider                | 图片的 Provider                                                                                        | -    |\n| invertColors                 | 是否反转颜色                                                                                           | -    |\n| imageStreamKey               | 图片流的唯一键                                                                                         | -    |\n| reLoadImage()                | 如果图片加载失败，你可以通过调用这个方法来重新加载图片                                                 | -    |\n| completedWidget              | 返回图片完成的 Widget，它包含手势以及裁剪                                                              | -    |\n| loadingProgress              | 返回网络图片加载进度 (ImageChunkEvent )                                                                | -    |\n\n```dart\nabstract class ExtendedImageState {\n  void reLoadImage();\n  ImageInfo get extendedImageInfo;\n  LoadState get extendedImageLoadState;\n\n  ///return widget which from LoadStateChanged function  immediately\n  bool returnLoadStateChangedWidget;\n\n  ImageProvider get imageProvider;\n\n  bool get invertColors;\n\n  Object get imageStreamKey;\n\n  Widget get completedWidget;\n}\n```\n\n### 例子\n\n```dart\nExtendedImage.network(\n  url,\n  width: ScreenUtil.instance.setWidth(600),\n  height: ScreenUtil.instance.setWidth(400),\n  fit: BoxFit.fill,\n  cache: true,\n  loadStateChanged: (ExtendedImageState state) {\n    switch (state.extendedImageLoadState) {\n      case LoadState.loading:\n        _controller.reset();\n        return Image.asset(\n          \"assets/loading.gif\",\n          fit: BoxFit.fill,\n        );\n        break;\n      ///if you don't want override completed widget\n      ///please return null or state.completedWidget\n      //return null;\n      //return state.completedWidget;\n      case LoadState.completed:\n        _controller.forward();\n        return FadeTransition(\n          opacity: _controller,\n          child: ExtendedRawImage(\n            image: state.extendedImageInfo?.image,\n            width: ScreenUtil.instance.setWidth(600),\n            height: ScreenUtil.instance.setWidth(400),\n          ),\n        );\n        break;\n      case LoadState.failed:\n        _controller.reset();\n        return GestureDetector(\n          child: Stack(\n            fit: StackFit.expand,\n            children: \u003cWidget\u003e[\n              Image.asset(\n                \"assets/failed.jpg\",\n                fit: BoxFit.fill,\n              ),\n              Positioned(\n                bottom: 0.0,\n                left: 0.0,\n                right: 0.0,\n                child: Text(\n                  \"load image failed, click to reload\",\n                  textAlign: TextAlign.center,\n                ),\n              )\n            ],\n          ),\n          onTap: () {\n            state.reLoadImage();\n          },\n        );\n        break;\n    }\n  },\n)\n```\n\n## 缩放拖拽\n\n![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_image/zoom.gif)\n\nExtendedImage\n\n| 参数                     | 描述                                                                  | 默认 |\n| ------------------------ | --------------------------------------------------------------------- | ---- |\n| mode                     | 图片模式，默认/手势/编辑 (none, gesture, editor)                      | none |\n| initGestureConfigHandler | 手势配置的回调(图片加载完成时).你可以根据图片的信息比如宽高，来初始化 | -    |\n| onDoubleTap              | 支持手势的时候，双击回调                                              | -    |\n| extendedImageGestureKey  | 你可以通过这个key来手动控制缩放和平移                                 | -    |\n\n\nGestureConfig\n\n| 参数              | 描述                                                                                                         | 默认值                       |\n| ----------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------- |\n| minScale          | 缩放最小值                                                                                                   | 0.8                          |\n| animationMinScale | 缩放动画最小值，当缩放结束时回到 minScale 值                                                                 | minScale \\* 0.8              |\n| maxScale          | 缩放最大值                                                                                                   | 5.0                          |\n| animationMaxScale | 缩放动画最大值，当缩放结束时回到 maxScale 值                                                                 | maxScale \\* 1.2              |\n| speed             | 缩放拖拽速度，与用户操作成正比                                                                               | 1.0                          |\n| inertialSpeed     | 拖拽惯性速度，与惯性速度成正比                                                                               | 100                          |\n| cacheGesture      | 是否缓存手势状态，可用于 ExtendedImageGesturePageView 中保留状态，**使用 clearGestureDetailsCache 方法清除** | false                        |\n| inPageView        | 是否使用 ExtendedImageGesturePageView 展示图片                                                               | false                        |\n| initialAlignment  | 当图片的初始化缩放大于 1.0 的时候，根据相对位置初始化图片                                                    | InitialAlignment.center      |\n| hitTestBehavior   | 设置hittest的行为                                                                                            | HitTestBehavior.deferToChild |\n\n```dart\nExtendedImage.network(\n  imageTestUrl,\n  fit: BoxFit.contain,\n  //enableLoadState: false,\n  mode: ExtendedImageMode.gesture,\n  initGestureConfigHandler: (state) {\n    return GestureConfig(\n        minScale: 0.9,\n        animationMinScale: 0.7,\n        maxScale: 3.0,\n        animationMaxScale: 3.5,\n        speed: 1.0,\n        inertialSpeed: 100.0,\n        initialScale: 1.0,\n        inPageView: false,\n        initialAlignment: InitialAlignment.center,\n        );\n  },\n)\n```\n\n### 双击图片动画\n\n支持双击动画，具体双击图片什么样子的效果，可以自定义\n\n```dart\nonDoubleTap: (ExtendedImageGestureState state) {\n  ///you can use define pointerDownPosition as you can,\n  ///default value is double tap pointer down position.\n  var pointerDownPosition = state.pointerDownPosition;\n  double begin = state.gestureDetails.totalScale;\n  double end;\n\n  //remove old\n  _animation?.removeListener(animationListener);\n\n  //stop pre\n  _animationController.stop();\n\n  //reset to use\n  _animationController.reset();\n\n  if (begin == doubleTapScales[0]) {\n    end = doubleTapScales[1];\n  } else {\n    end = doubleTapScales[0];\n  }\n\n  animationListener = () {\n    //print(_animation.value);\n    state.handleDoubleTap(\n        scale: _animation.value,\n        doubleTapPosition: pointerDownPosition);\n  };\n  _animation = _animationController\n      .drive(Tween\u003cdouble\u003e(begin: begin, end: end));\n\n  _animation.addListener(animationListener);\n\n  _animationController.forward();\n},\n```\n\n## 图片编辑\n\n![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_image/editor.gif)\n\n```dart\n    ExtendedImage.network(\n      imageTestUrl,\n      fit: BoxFit.contain,\n      mode: ExtendedImageMode.editor,\n      extendedImageEditorKey: editorKey,\n      initEditorConfigHandler: (state) {\n        return EditorConfig(\n            maxScale: 8.0,\n            cropRectPadding: EdgeInsets.all(20.0),\n            hitTestSize: 20.0,\n            cropAspectRatio: _aspectRatio.aspectRatio);\n      },\n    );\n```\n\nExtendedImage\n\n| 参数                     | 描述                                                                    | 默认 |\n| ------------------------ | ----------------------------------------------------------------------- | ---- |\n| mode                     | 图片模式，默认/手势/编辑 (none, gesture, editor)                        | none |\n| initGestureConfigHandler | 编辑器配置的回调(图片加载完成时).你可以根据图片的信息比如宽高，来初始化 | -    |\n| extendedImageEditorKey   | key of ExtendedImageEditorState 用于裁剪旋转翻转                        | -    |\n\nEditorConfig\n\n| 参数                   | 描述                                                                               | 默认                                                         |\n| ---------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------ |\n| maxScale               | 最大的缩放倍数                                                                     | 5.0                                                          |\n| cropRectPadding        | 裁剪框跟图片 layout 区域之间的距离。最好是保持一定距离，不然裁剪框边界很难进行拖拽 | EdgeInsets.all(20.0)                                         |\n| cornerSize             | 裁剪框四角图形的大小                                                               | Size(30.0, 5.0)                                              |\n| cornerColor            | 裁剪框四角图形的颜色                                                               | primaryColor                                                 |\n| lineColor              | 裁剪框线的颜色                                                                     | scaffoldBackgroundColor.withOpacity(0.7)                     |\n| lineHeight             | 裁剪框线的高度                                                                     | 0.6                                                          |\n| editorMaskColorHandler | 蒙层的颜色回调，你可以根据是否手指按下来设置不同的蒙层颜色                         | scaffoldBackgroundColor.withOpacity(pointerDown ? 0.4 : 0.8) |\n| hitTestSize            | 裁剪框四角以及边线能够拖拽的区域的大小                                             | 20.0                                                         |\n| animationDuration      | 当裁剪框拖拽变化结束之后，自动适应到中间的动画的时长                               | Duration(milliseconds: 200)                                  |\n| tickerDuration         | 当裁剪框拖拽变化结束之后，多少时间才触发自动适应到中间的动画                       | Duration(milliseconds: 400)                                  |\n| cropAspectRatio        | 裁剪框的宽高比                                                                     | null(无宽高比)                                               |\n| initialCropAspectRatio | 初始化的裁剪框的宽高比                                                             | null(custom: 填充满图片原始宽高比)                           |\n| initCropRectType       | 剪切框的初始化类型(根据图片初始化区域或者图片的 layout 区域)                       | imageRect                                                    |\n| hitTestBehavior        | 设置hittest的行为                                                                  | HitTestBehavior.deferToChild                                 |\n| controller             | 提供旋转,翻转,撤销,重做,重置, 重新设置裁剪比例等操作                               | null                                                         |\n\n\n### 裁剪框的宽高比\n\n这是一个 double 类型，你可以自定义裁剪框的宽高比。\n如果为 null，那就没有宽高比限制。\n如果小于等于 0，宽高比等于图片的宽高比。\n下面是一些定义好了的宽高比\n\n```dart\nclass CropAspectRatios {\n  /// no aspect ratio for crop\n  static const double custom = null;\n\n  /// the same as aspect ratio of image\n  /// [cropAspectRatio] is not more than 0.0, it's original\n  static const double original = 0.0;\n\n  /// ratio of width and height is 1 : 1\n  static const double ratio1_1 = 1.0;\n\n  /// ratio of width and height is 3 : 4\n  static const double ratio3_4 = 3.0 / 4.0;\n\n  /// ratio of width and height is 4 : 3\n  static const double ratio4_3 = 4.0 / 3.0;\n\n  /// ratio of width and height is 9 : 16\n  static const double ratio9_16 = 9.0 / 16.0;\n\n  /// ratio of width and height is 16 : 9\n  static const double ratio16_9 = 16.0 / 9.0;\n}\n```\n\n### 裁剪图层 Painter\n\n你现在可以通过覆写 [EditorConfig.editorCropLayerPainter] 里面的方法来自定裁剪图层.\n\n```dart\nclass EditorCropLayerPainter {\n  const EditorCropLayerPainter();\n  void paint(\n    Canvas canvas,\n    Size size,\n    ExtendedImageCropLayerPainter painter,\n    Rect rect,\n  ) {\n    // Draw the mask layer\n    paintMask(canvas, rect, painter);\n\n    // Draw the grid lines\n    paintLines(canvas, size, painter);\n\n    // Draw the corners of the crop area\n    paintCorners(canvas, size, painter);\n  }\n\n  /// draw crop layer corners\n  void paintCorners(\n      Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {\n  }\n\n  /// draw crop layer lines\n  void paintMask(\n      Canvas canvas, Rect rect, ExtendedImageCropLayerPainter painter) {\n  }\n  \n\n  /// draw crop layer lines\n  void paintLines(\n      Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {\n  } \n}\n```\n\n### 翻转、旋转、重新设置裁剪比例、撤消、重做、重置\n\n#### 翻转\n\n```dart\n   _editorController.flip();\n\n  void flip({\n    bool animation = false,\n    Duration duration = const Duration(milliseconds: 200),\n  })\n```\n\n\n\n #### 旋转\n\n```dart\n   _editorController.rotate();\n\n  void rotate({\n    double degrees = 90,\n    bool animation = false,\n    Duration duration = const Duration(milliseconds: 200),\n    bool rotateCropRect = true,\n  })\n```\n\n\n\n #### 重新设置裁剪比例\n\n```dart\n   _editorController.updateCropAspectRatio(CropAspectRatios.ratio4_3);\n```\n\n\n\n #### 撤消\n\n```dart\n  bool canUndo = _editorController.canUndo;\n   _editorController.undo();\n\n```\n\n #### 重做\n\n```dart\n  bool canRedo = _editorController.canRedo;\n   _editorController.redo();\n```\n\n#### 重置\n\n```dart\n   _editorController.reset();\n```\n\n#### 历史\n\n```dart\n   _editorController.currentIndex;\n   _editorController.history;\n   _editorController.saveCurrentState();   \n```\n\n#### 配置更新\n\n```dart\n   _editorController.updateConfig(EditorConfig config);\n   _editorController.config;\n```\n\n### 裁剪数据\n\n#### 使用 dart 库(稳定)\n\n- 添加 [Image](https://github.com/brendan-duncan/image) 库到 pubspec.yaml, 它是用来裁剪/旋转/翻转图片数据的\n\n```yaml\ndependencies:\n  image: any\n```\n\n- 从 ExtendedImageEditorState 中获取裁剪区域以及图片数据\n\n```dart\n  ///crop rect base on raw image\n  final Rect cropRect = state.getCropRect();\n\n  var data = state.rawImageData;\n```\n\n- 将 flutter 的图片数据转换为 image 库的数据\n\n```dart\n  /// it costs much time and blocks ui.\n  //Image src = decodeImage(data);\n\n  /// it will not block ui with using isolate.\n  //Image src = await compute(decodeImage, data);\n  //Image src = await isolateDecodeImage(data);\n  final lb = await loadBalancer;\n  Image src = await lb.run\u003cImage, List\u003cint\u003e\u003e(decodeImage, data);\n```\n\n- 翻转，旋转，裁剪数据\n\n```dart\n  //相机拍照的图片带有旋转，处理之前需要去掉\n  image = bakeOrientation(image);\n  if (editAction.hasRotateDegrees) {\n    image = copyRotate(image, angle: editAction.rotateDegrees);\n  }\n\n  if (editAction.flipY) {\n    image = flip(image, direction: FlipDirection.horizontal);\n  }\n\n  if (editAction.needCrop) {\n    image = copyCrop(\n      image,\n      x: cropRect.left.toInt(),\n      y: cropRect.top.toInt(),\n      width: cropRect.width.toInt(),\n      height: cropRect.height.toInt(),\n    );\n  }\n```\n\n- 将数据转为为图片的元数据\n\n获取到的将是图片的元数据，你可以使用它来保存或者其他的一些用途\n\n```dart\n  /// you can encode your image\n  ///\n  /// it costs much time and blocks ui.\n  //var fileData = encodeJpg(src);\n\n  /// it will not block ui with using isolate.\n  //var fileData = await compute(encodeJpg, src);\n  //var fileData = await isolateEncodeImage(src);\n  var fileData = await lb.run\u003cList\u003cint\u003e, Image\u003e(encodeJpg, src);\n```\n\n#### 使用原生库(快速)\n\n- 添加 [ImageEditor](https://github.com/fluttercandies/flutter_image_editor) 库到 pubspec.yaml, 它是用来裁剪/旋转/翻转图片数据的。\n\n```yaml\ndependencies:\n  image_editor: any\n```\n\n- 从 ExtendedImageEditorState 中获取裁剪区域以及图片数据\n\n```dart\n  ///crop rect base on raw image\n  final Rect cropRect = state.getCropRect();\n\n  final img = state.rawImageData;\n```\n\n- 准备裁剪选项\n\n```dart\n  if (action.hasRotateDegrees) {\n    final int rotateDegrees = action.rotateDegrees.toInt();\n    option.addOption(RotateOption(rotateDegrees));\n  }\n  if (action.flipY) {\n    option.addOption(const FlipOption(horizontal: true, vertical: false));\n  }\n\n  if (action.needCrop) {\n    Rect cropRect = imageEditorController.getCropRect()!;\n    option.addOption(ClipOption.fromRect(cropRect));\n  }\n```\n\n- 使用 editImage 方法进行裁剪\n\n获取到的将是图片的元数据，你可以使用它来保存或者其他的一些用途\n\n```dart\n  final result = await ImageEditor.editImage(\n    image: img,\n    imageEditorOption: option,\n  );\n```\n\n[more detail](https://github.com/fluttercandies/extended_image/blob/master/example/lib/common/utils/crop_editor_helper.dart)\n\n## 图片浏览\n\n支持跟微信/掘金一样的图片查看效果\n\nExtendedImageGesturePageView 跟官方 PageView 一样的使用，不同的是，它避免了跟缩放拖拽手势冲突\n\n支持缓存手势的状态，就是说你缩放了图片，然后下一个，再回到之前的图片，图片的缩放状态可以保存，\n如果你缓存了手势，记住在合适的时候使用 clearGestureDetailsCache()清除掉，比如页面销毁的时候\n\n![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_image/photo_view.gif)\n\nExtendedImageGesturePageView\n\n| parameter   | description                                                              | default |\n| ----------- | ------------------------------------------------------------------------ | ------- |\n| canMovePage | 是否滑动页面.有些场景如果 Scale 大于 1.0，并不想滑动页面，可以返回 false | true    |\n\nGestureConfig\n\n| 参数         | 描述                                                                                                     | 默认  |\n| ------------ | -------------------------------------------------------------------------------------------------------- | ----- |\n| cacheGesture | 是否缓存手势状态，可用于 ExtendedImageGesturePageView 中保留状态，使用 clearGestureDetailsCache 方法清除 | false |\n| inPageView   | 是否使用 ExtendedImageGesturePageView 展示图片                                                           | false |\n\n```dart\nExtendedImageGesturePageView.builder(\n  itemBuilder: (BuildContext context, int index) {\n    var item = widget.pics[index].picUrl;\n    Widget image = ExtendedImage.network(\n      item,\n      fit: BoxFit.contain,\n      mode: ExtendedImageMode.gesture,\n      gestureConfig: GestureConfig(\n        inPageView: true, initialScale: 1.0,\n        //you can cache gesture state even though page view page change.\n        //remember call clearGestureDetailsCache() method at the right time.(for example,this page dispose)\n        cacheGesture: false\n      ),\n    );\n    image = Container(\n      child: image,\n      padding: EdgeInsets.all(5.0),\n    );\n    if (index == currentIndex) {\n      return Hero(\n        tag: item + index.toString(),\n        child: image,\n      );\n    } else {\n      return image;\n    }\n  },\n  itemCount: widget.pics.length,\n  onPageChanged: (int index) {\n    currentIndex = index;\n    rebuild.add(index);\n  },\n  controller: PageController(\n    initialPage: currentIndex,\n  ),\n  scrollDirection: Axis.horizontal,\n),\n```\n\n## 滑动退出页面\n\n支持微信掘金滑动退出页面的效果\n\n![img](https://raw.githubusercontent.com/fluttercandies/Flutter_Candies/master/gif/extended_image/slide.gif)\n\n### 首先开启滑动退出页面效果\n\nExtendedImage\n\n| parameter                 | description                                                                              | default |\n| ------------------------- | ---------------------------------------------------------------------------------------- | ------- |\n| enableSlideOutPage        | 是否开启滑动退出页面效果                                                                 | false   |\n| heroBuilderForSlidingPage | 滑动退出页面的 transform 必须作用在 Hero 上面，这样在退出页面的时候，Hero 动画才不会奇怪 | null    |\n\n### 把你的页面用 ExtendedImageSlidePage 包一下\n\n注意：onSlidingPage 回调，你可以使用它来设置滑动页面的时候,页面上其他元素的状态。但是注意别直接使用 setState 来刷新，因为这样会导致 ExtendedImage 的状态重置掉，你应该只通知需要刷新的 Widgets 进行刷新\n\n```dart\n    return ExtendedImageSlidePage(\n      child: result,\n      slideAxis: SlideAxis.both,\n      slideType: SlideType.onlyImage,\n      onSlidingPage: (state) {\n        ///you can change other widgets' state on page as you want\n        ///base on offset/isSliding etc\n        //var offset= state.offset;\n        var showSwiper = !state.isSliding;\n        if (showSwiper != _showSwiper) {\n          // do not setState directly here, the image state will change,\n          // you should only notify the widgets which are needed to change\n          // setState(() {\n          // _showSwiper = showSwiper;\n          // });\n\n          _showSwiper = showSwiper;\n          rebuildSwiper.add(_showSwiper);\n        }\n      },\n    );\n```\n\nExtendedImageGesturePage 的参数\n\n| parameter                  | description                                                             | default                           |\n| -------------------------- | ----------------------------------------------------------------------- | --------------------------------- |\n| child                      | 需要包裹的页面                                                          | -                                 |\n| slidePageBackgroundHandler | 在滑动页面的时候根据 Offset 自定义整个页面的背景色                      | defaultSlidePageBackgroundHandler |\n| slideScaleHandler          | 在滑动页面的时候根据 Offset 自定义整个页面的缩放值                      | defaultSlideScaleHandler          |\n| slideEndHandler            | 滑动页面结束的时候计算是否需要 pop 页面                                 | defaultSlideEndHandler            |\n| slideAxis                  | 滑动页面的方向（both,horizontal,vertical）,掘金是 vertical，微信是 Both | both                              |\n| resetPageDuration          | 滑动结束，如果不 pop 页面，整个页面回弹动画的时间                       | milliseconds: 500                 |\n| slideType                  | 滑动整个页面还是只是图片(wholePage/onlyImage)                           | SlideType.onlyImage               |\n| onSlidingPage              | 滑动页面的回调，你可以在这里改变页面上其他元素的状态                    | -                                 |\n| slideOffsetHandler         | 在滑动页面的时候自定义 Offset                                           | -                                 |\n\n下面是默认实现，你也可以根据你的喜好，来定义属于自己方式\n\n```dart\nColor defaultSlidePageBackgroundHandler(\n    {Offset offset, Size pageSize, Color color, SlideAxis pageGestureAxis}) {\n  double opacity = 0.0;\n  if (pageGestureAxis == SlideAxis.both) {\n    opacity = offset.distance /\n        (Offset(pageSize.width, pageSize.height).distance / 2.0);\n  } else if (pageGestureAxis == SlideAxis.horizontal) {\n    opacity = offset.dx.abs() / (pageSize.width / 2.0);\n  } else if (pageGestureAxis == SlideAxis.vertical) {\n    opacity = offset.dy.abs() / (pageSize.height / 2.0);\n  }\n  return color.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));\n}\n\nbool defaultSlideEndHandler(\n    {Offset offset, Size pageSize, SlideAxis pageGestureAxis}) {\n  if (pageGestureAxis == SlideAxis.both) {\n    return offset.distance \u003e\n        Offset(pageSize.width, pageSize.height).distance / 3.5;\n  } else if (pageGestureAxis == SlideAxis.horizontal) {\n    return offset.dx.abs() \u003e pageSize.width / 3.5;\n  } else if (pageGestureAxis == SlideAxis.vertical) {\n    return offset.dy.abs() \u003e pageSize.height / 3.5;\n  }\n  return true;\n}\n\ndouble defaultSlideScaleHandler(\n    {Offset offset, Size pageSize, SlideAxis pageGestureAxis}) {\n  double scale = 0.0;\n  if (pageGestureAxis == SlideAxis.both) {\n    scale = offset.distance / Offset(pageSize.width, pageSize.height).distance;\n  } else if (pageGestureAxis == SlideAxis.horizontal) {\n    scale = offset.dx.abs() / (pageSize.width / 2.0);\n  } else if (pageGestureAxis == SlideAxis.vertical) {\n    scale = offset.dy.abs() / (pageSize.height / 2.0);\n  }\n  return max(1.0 - scale, 0.8);\n}\n```\n\n### 确保你的页面是透明背景的\n\n如果你设置 slideType =SlideType.onlyImage, 请确保的你页面是透明的，毕竟没法操控你页面上的颜色\n\n### Push 一个透明的页面\n\n这里我把官方的 MaterialPageRoute 和 CupertinoPageRoute 拷贝出来了，\n改为 TransparentMaterialPageRoute/TransparentCupertinoPageRoute，因为它们的 opaque 不能设置为 false\n\n```dart\n  Navigator.push(\n    context,\n    Platform.isAndroid\n        ? TransparentMaterialPageRoute(builder: (_) =\u003e page)\n        : TransparentCupertinoPageRoute(builder: (_) =\u003e page),\n  );\n```\n\n[滑动退出页面相关代码演示 1](https://github.com/fluttercandies/extended_image/blob/master/example/lib/common/widget/crop_image.dart)\n\n[滑动退出页面相关代码演示 2](https://github.com/fluttercandies/extended_image/blob/master/example/lib/common/widget/pic_swiper.dart)\n\n## Border BorderRadius Shape\n\nExtendedImage\n\n| 参数         | 描述                                               | 默认 |\n| ------------ | -------------------------------------------------- | ---- |\n| border       | 跟官方的含义一样，你可以通过它设置边框             | -    |\n| borderRadius | 跟官方的含义一样，你可以通过它设置圆角             |\n| shape        | 跟官方的含义一样，你可以通过它设置裁剪（矩形和圆） | -    |\n\n```dart\nExtendedImage.network(\n  url,\n  width: ScreenUtil.instance.setWidth(400),\n  height: ScreenUtil.instance.setWidth(400),\n  fit: BoxFit.fill,\n  cache: true,\n  border: Border.all(color: Colors.red, width: 1.0),\n  shape: boxShape,\n  borderRadius: BorderRadius.all(Radius.circular(30.0)),\n),\n```\n\n![img](https://raw.githubusercontent.com/fluttercandies/Flutter_Candies/master/gif/extended_image/image.gif)\n\n## 清除缓存和保存\n\n### 清除缓存\n\n清除本地缓存，可以调用 clearDiskCachedImages 方法\n\n```dart\n// Clear the disk cache directory then return if it succeed.\n///  \u003cparam name=\"duration\"\u003etimespan to compute whether file has expired or not\u003c/param\u003e\nFuture\u003cbool\u003e clearDiskCachedImages({Duration duration})\n```\n\n根据某一个 url 清除缓存， 可以调用 clearDiskCachedImage 方法.\n\n```dart\n/// clear the disk cache image then return if it succeed.\n///  \u003cparam name=\"url\"\u003eclear specific one\u003c/param\u003e\nFuture\u003cbool\u003e clearDiskCachedImage(String url) async {\n```\n\n根据 url 获取缓存图片文件\n\n```dart\nFuture\u003cFile\u003e getCachedImageFile(String url) async {\n```\n\n清除内存缓存，可以调用 clearMemoryImageCache 方法\n\n```dart\n///clear all of image in memory\n clearMemoryImageCache();\n\n/// get ImageCache\n getMemoryImageCache() ;\n```\n\n### 保存网络图片\n\n这是一个例子，使用到[image_picker_saver](https://github.com/cnhefang/image_picker_saver)\n插件，ExtendedImage 做的只是提供网络图片的数据源\n\n```dart\n///save network image to photo\nFuture\u003cbool\u003e saveNetworkImageToPhoto(String url, {bool useCache: true}) async {\n  var data = await getNetworkImageData(url, useCache: useCache);\n  var filePath = await ImagePickerSaver.saveFile(fileData: data);\n  return filePath != null \u0026\u0026 filePath != \"\";\n}\n```\n\n## 显示裁剪图片\n\n![img](https://raw.githubusercontent.com/fluttercandies/Flutter_Candies/master/gif/extended_image/crop.gif)\n\n你可以通过\n[ExtendedRawImage](https://github.com/fluttercandies/extended_image/blob/master/lib/src/image/raw_image.dart)(可以在状态回调的时候使用),sourceRect 是你想要显示图片的哪一部分，这个在各个 app 里面应该是比较常见的操作\n\n```dart\nExtendedRawImage(\n  image: image,\n  width: num400,\n  height: num300,\n  fit: BoxFit.fill,\n  sourceRect: Rect.fromLTWH(\n      (image.width - width) / 2.0, 0.0, width, image.height.toDouble()),\n)\n```\n\n## 绘制\n\n![img](https://raw.githubusercontent.com/fluttercandies/Flutter_Candies/master/gif/extended_image/paint.gif)\n\n提供了 BeforePaintImage and AfterPaintImage 两个回调, 这样你就能绘制你想要的东西或者进图片进行 Clip。\n\nExtendedImage\n\n| parameter        | description    | default |\n| ---------------- | -------------- | ------- |\n| beforePaintImage | 在绘制图片之前 | -       |\n| afterPaintImage  | 在绘制图片之后 | -       |\n\n```dart\n  ExtendedImage.network(\n    url,\n    width: ScreenUtil.instance.setWidth(400),\n    height: ScreenUtil.instance.setWidth(400),\n    fit: BoxFit.fill,\n    cache: true,\n    beforePaintImage: (Canvas canvas, Rect rect, ui.Image image) {\n      if (paintType == PaintType.ClipHeart) {\n        if (!rect.isEmpty) {\n          canvas.save();\n          canvas.clipPath(clipheart(rect, canvas));\n        }\n      }\n      return false;\n    },\n    afterPaintImage: (Canvas canvas, Rect rect, ui.Image image) {\n      if (paintType == PaintType.ClipHeart) {\n        if (!rect.isEmpty) canvas.restore();\n      } else if (paintType == PaintType.PaintHeart) {\n        canvas.drawPath(\n            clipheart(rect, canvas),\n            Paint()\n              ..colorFilter =\n                  ColorFilter.mode(Color(0x55ea5504), BlendMode.srcIn)\n              ..isAntiAlias = false\n              ..filterQuality = FilterQuality.low);\n      }\n    },\n  );\n```\n\n在例子中可以看到把图片 Clip 成了一个桃心，你也可以根据你的要求，做出不同的 Clip\n[绘制例子](https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/paint_image_demo.dart)\n[下拉刷新头当中，也使用了这个技巧](https://github.com/fluttercandies/extended_image/blob/master/example/lib/common/widget/push_to_refresh_header.dart)\n\n## Notch\n\n设置图片初始化的 Insets，不会影响放大。\n\nExtendedImage\n\n| parameter    | description              | default         |\n| ------------ | ------------------------ | --------------- |\n| layoutInsets | 设置图片初始化时候的边距 | EdgeInsets.zero |\n\n```dart\n  ExtendedImage.network(\n    url,\n    fit: BoxFit.contain,\n    layoutInsets: MediaQuery.of(context).padding\n  );\n```\n\n## 内存使用\n\n现在你可以通过下面设置来减少图片内存的占用.\n\n* ExtendedResizeImage\n\n| parameter                                                | description                                                                   | default  |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- | -------- |\n| [ExtendedResizeImage.compressionRatio]                   | 图片压缩率，大于0小于1                                                        | null     |\n| [ExtendedResizeImage.maxBytes]                           | 图片的大小的最大值. 默认值为 50KB. 这是图片实际的的大小，而不是解码之后的大小 | 50 \u003c\u003c 10 |\n| [ExtendedResizeImage.width]/[ExtendedResizeImage.height] | 宽和高用于decode和缓存. 跟官方的[ResizeImage]一致。                           | null     |\n\n```dart\n    ExtendedImage.network(\n      'imageUrl',  \n      compressionRatio: 0.1,\n      maxBytes: null,\n      cacheWidth: null,\n      cacheHeight: null,  \n    )\n\n    ExtendedImage(\n      image: ExtendedResizeImage(\n        ExtendedNetworkImageProvider(\n          'imageUrl',  \n        ),\n        compressionRatio: 0.1,\n        maxBytes: null,\n        width: null,\n        height: null,\n      ),\n    )\n```\n\n* clearMemoryCacheWhenDispose\n\n| parameter                   | description                                                                                                                                                    | default |\n| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |\n| clearMemoryCacheWhenDispose | 在Flutter 2.0之后也许不会起作用, 因为没法在图片没有完成之前释放掉(https://github.com/fluttercandies/extended_image/issues/317).  现在只会释放已完成的图片资源. | false   |\n\n\n\n```dart\n   ExtendedImage.network(\n     'imageUrl',     \n     clearMemoryCacheWhenDispose: true,\n   )\n```\n\n* imageCacheName\n\n| parameter      | description                                                                               | default |\n| -------------- | ----------------------------------------------------------------------------------------- | ------- |\n| imageCacheName | 你可以指定一个 ImageCache 来缓存一些图片。这样你可以一起处理它们，不会影响其他的图片缓存. | null    |\n\n```dart\n   ExtendedImage.network(\n     'imageUrl',  \n     imageCacheName: 'MemoryUsage',\n   )\n     \n  /// clear when this page is disposed   \n  @override\n  void dispose() {\n    // clear ImageCache which named 'MemoryUsage'\n    clearMemoryImageCache(imageCacheName);\n    super.dispose();\n  }   \n```\n\n## 其他 APIs\n\nExtendedImage\n\n| 参数                        | 描述                                     | 默认  |\n| --------------------------- | ---------------------------------------- | ----- |\n| enableMemoryCache           | 是否缓存到内存                           | true  |\n| clearMemoryCacheIfFailed    | 如果图片加载失败，是否清掉内存缓存       | true  |\n| clearMemoryCacheWhenDispose | 如果图片从 tree 中移除，是否清掉内存缓存 | false |\n","funding_links":["http://zmtzawqlp.gitee.io/my_images/images/qrcode.png"],"categories":["UI Components","Packages","Dart"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluttercandies%2Fextended_image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluttercandies%2Fextended_image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluttercandies%2Fextended_image/lists"}