Skip to content

Latest commit

 

History

History
1036 lines (818 loc) · 41.1 KB

README-ZH.md

File metadata and controls

1036 lines (818 loc) · 41.1 KB

extended_image

pub package GitHub stars GitHub forks GitHub license GitHub issues flutter-candies

文档语言: English | 中文简体

强大的官方 Image 扩展组件, 支持加载以及失败显示,缓存网络图片,缩放拖拽图片,图片浏览(微信掘金效果),滑动退出页面(微信掘金效果),编辑图片(裁剪旋转翻转),保存,绘制自定义效果等功能

Web demo for ExtendedImage

目录

导入

  • 空安全
environment:
  sdk: '>=2.12.0 <3.0.0'
  flutter: '>=2.0'
dependencies:
  extended_image: ^4.0.0
  • 非空安全

1.22.6 到 2.0, Flutter Api 有 breaking change,所以 1.22.6 以下的,请使用非空安全版本

environment:
  sdk: '>=2.6.0 <2.12.0'
  flutter: '>1.17.0 <=1.22.6'
dependencies:
  extended_image: ^3.0.0-non-null-safety

缓存网络图片

简单使用

你可以直接使用 ExtendedImage.network,这跟官方是一样。

ExtendedImage.network(
  url,
  width: ScreenUtil.instance.setWidth(400),
  height: ScreenUtil.instance.setWidth(400),
  fit: BoxFit.fill,
  cache: true,
  border: Border.all(color: Colors.red, width: 1.0),
  shape: boxShape,
  borderRadius: BorderRadius.all(Radius.circular(30.0)),
  //cancelToken: cancellationToken,
)

使用 ExtendedNetworkImageProvider

你也可以通过ExtendedNetworkImageProvider,设置更多的网络请求的参数

ExtendedNetworkImageProvider(
  this.url, {
  this.scale = 1.0,
  this.headers,
  this.cache: false,
  this.retries = 3,
  this.timeLimit,
  this.timeRetry = const Duration(milliseconds: 100),
  CancellationToken cancelToken,
})  : assert(url != null),
      assert(scale != null),
      cancelToken = cancelToken ?? CancellationToken();
参数 描述 默认
url 网络请求地址 required
scale ImageInfo 中的 scale 1.0
headers HttpClient 的 headers -
cache 是否缓存到本地 false
retries 请求尝试次数 3
timeLimit 请求超时 -
timeRetry 请求重试间隔 milliseconds: 100
cancelToken 用于取消请求的 Token CancellationToken()

当然你也可以继承任何的 ExtendedProvider,并且覆写 instantiateImageCodec 方法,这样你可以统一处理图片的元数据,比如进行压缩图片。

加载状态

Extended Image 一共有 3 种状态,分别是正在加载,完成,失败(loading,completed,failed),你可以通过实现 loadStateChanged 回调来定义显示的效果

loadStateChanged 不仅仅只在网络图片中可以使用, 如果你的图片很大,需要长时间加载, 你可以把 enableLoadState 设置为了 true,这样也会有状态回调了,(默认只有网络图片,enableLoadState 为 true)

img

注意:

  • 如果你不想重写某个状态,那么请返回 null

  • 如果你想重写完成图片的 size 或者 sourceRect, 你可以通过使用 ExtendedRawImage 来完成

  • 如果你想增加一些新效果 (比如动画), 你可以重写并且使用 ExtendedImageState.completedWidget

  • ExtendedImageState.completedWidget 包含手势或者裁剪, 这样你不会丢失它们

/// custom load state widget if you want
    final LoadStateChanged loadStateChanged;

enum LoadState {
  //loading
  loading,
  //completed
  completed,
  //failed
  failed
}

  ///whether has loading or failed state
  ///default is false
  ///but network image is true
  ///better to set it's true when your image is big and take some time to ready
  final bool enableLoadState;

ExtendedImageState 状态回调

参数/方法 描述 默认
extendedImageInfo 图片的信息,包括底层 image 和 image 的大小 -
extendedImageLoadState 状态(loading,completed,failed) -
returnLoadStateChangedWidget 如果这个为 true 的话,状态回调返回的 widget 将不会对(width/height/gesture/border/shape)等效果进行包装 -
imageProvider 图片的 Provider -
invertColors 是否反转颜色 -
imageStreamKey 图片流的唯一键 -
reLoadImage() 如果图片加载失败,你可以通过调用这个方法来重新加载图片 -
completedWidget 返回图片完成的 Widget,它包含手势以及裁剪 -
loadingProgress 返回网络图片加载进度 (ImageChunkEvent ) -
abstract class ExtendedImageState {
  void reLoadImage();
  ImageInfo get extendedImageInfo;
  LoadState get extendedImageLoadState;

  ///return widget which from LoadStateChanged function  immediately
  bool returnLoadStateChangedWidget;

  ImageProvider get imageProvider;

  bool get invertColors;

  Object get imageStreamKey;

  Widget get completedWidget;
}

例子

ExtendedImage.network(
  url,
  width: ScreenUtil.instance.setWidth(600),
  height: ScreenUtil.instance.setWidth(400),
  fit: BoxFit.fill,
  cache: true,
  loadStateChanged: (ExtendedImageState state) {
    switch (state.extendedImageLoadState) {
      case LoadState.loading:
        _controller.reset();
        return Image.asset(
          "assets/loading.gif",
          fit: BoxFit.fill,
        );
        break;
      ///if you don't want override completed widget
      ///please return null or state.completedWidget
      //return null;
      //return state.completedWidget;
      case LoadState.completed:
        _controller.forward();
        return FadeTransition(
          opacity: _controller,
          child: ExtendedRawImage(
            image: state.extendedImageInfo?.image,
            width: ScreenUtil.instance.setWidth(600),
            height: ScreenUtil.instance.setWidth(400),
          ),
        );
        break;
      case LoadState.failed:
        _controller.reset();
        return GestureDetector(
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Image.asset(
                "assets/failed.jpg",
                fit: BoxFit.fill,
              ),
              Positioned(
                bottom: 0.0,
                left: 0.0,
                right: 0.0,
                child: Text(
                  "load image failed, click to reload",
                  textAlign: TextAlign.center,
                ),
              )
            ],
          ),
          onTap: () {
            state.reLoadImage();
          },
        );
        break;
    }
  },
)

缩放拖拽

img

ExtendedImage

参数 描述 默认
mode 图片模式,默认/手势/编辑 (none, gesture, editor) none
initGestureConfigHandler 手势配置的回调(图片加载完成时).你可以根据图片的信息比如宽高,来初始化 -
onDoubleTap 支持手势的时候,双击回调 -
extendedImageGestureKey 你可以通过这个key来手动控制缩放和平移 -

GestureConfig

参数 描述 默认值
minScale 缩放最小值 0.8
animationMinScale 缩放动画最小值,当缩放结束时回到 minScale 值 minScale * 0.8
maxScale 缩放最大值 5.0
animationMaxScale 缩放动画最大值,当缩放结束时回到 maxScale 值 maxScale * 1.2
speed 缩放拖拽速度,与用户操作成正比 1.0
inertialSpeed 拖拽惯性速度,与惯性速度成正比 100
cacheGesture 是否缓存手势状态,可用于 ExtendedImageGesturePageView 中保留状态,使用 clearGestureDetailsCache 方法清除 false
inPageView 是否使用 ExtendedImageGesturePageView 展示图片 false
initialAlignment 当图片的初始化缩放大于 1.0 的时候,根据相对位置初始化图片 InitialAlignment.center
hitTestBehavior 设置hittest的行为 HitTestBehavior.deferToChild
ExtendedImage.network(
  imageTestUrl,
  fit: BoxFit.contain,
  //enableLoadState: false,
  mode: ExtendedImageMode.gesture,
  initGestureConfigHandler: (state) {
    return GestureConfig(
        minScale: 0.9,
        animationMinScale: 0.7,
        maxScale: 3.0,
        animationMaxScale: 3.5,
        speed: 1.0,
        inertialSpeed: 100.0,
        initialScale: 1.0,
        inPageView: false,
        initialAlignment: InitialAlignment.center,
        );
  },
)

双击图片动画

支持双击动画,具体双击图片什么样子的效果,可以自定义

onDoubleTap: (ExtendedImageGestureState state) {
  ///you can use define pointerDownPosition as you can,
  ///default value is double tap pointer down position.
  var pointerDownPosition = state.pointerDownPosition;
  double begin = state.gestureDetails.totalScale;
  double end;

  //remove old
  _animation?.removeListener(animationListener);

  //stop pre
  _animationController.stop();

  //reset to use
  _animationController.reset();

  if (begin == doubleTapScales[0]) {
    end = doubleTapScales[1];
  } else {
    end = doubleTapScales[0];
  }

  animationListener = () {
    //print(_animation.value);
    state.handleDoubleTap(
        scale: _animation.value,
        doubleTapPosition: pointerDownPosition);
  };
  _animation = _animationController
      .drive(Tween<double>(begin: begin, end: end));

  _animation.addListener(animationListener);

  _animationController.forward();
},

图片编辑

img

    ExtendedImage.network(
      imageTestUrl,
      fit: BoxFit.contain,
      mode: ExtendedImageMode.editor,
      extendedImageEditorKey: editorKey,
      initEditorConfigHandler: (state) {
        return EditorConfig(
            maxScale: 8.0,
            cropRectPadding: EdgeInsets.all(20.0),
            hitTestSize: 20.0,
            cropAspectRatio: _aspectRatio.aspectRatio);
      },
    );

ExtendedImage

参数 描述 默认
mode 图片模式,默认/手势/编辑 (none, gesture, editor) none
initGestureConfigHandler 编辑器配置的回调(图片加载完成时).你可以根据图片的信息比如宽高,来初始化 -
extendedImageEditorKey key of ExtendedImageEditorState 用于裁剪旋转翻转 -

EditorConfig

参数 描述 默认
maxScale 最大的缩放倍数 5.0
cropRectPadding 裁剪框跟图片 layout 区域之间的距离。最好是保持一定距离,不然裁剪框边界很难进行拖拽 EdgeInsets.all(20.0)
cornerSize 裁剪框四角图形的大小 Size(30.0, 5.0)
cornerColor 裁剪框四角图形的颜色 primaryColor
lineColor 裁剪框线的颜色 scaffoldBackgroundColor.withOpacity(0.7)
lineHeight 裁剪框线的高度 0.6
editorMaskColorHandler 蒙层的颜色回调,你可以根据是否手指按下来设置不同的蒙层颜色 scaffoldBackgroundColor.withOpacity(pointerDown ? 0.4 : 0.8)
hitTestSize 裁剪框四角以及边线能够拖拽的区域的大小 20.0
animationDuration 当裁剪框拖拽变化结束之后,自动适应到中间的动画的时长 Duration(milliseconds: 200)
tickerDuration 当裁剪框拖拽变化结束之后,多少时间才触发自动适应到中间的动画 Duration(milliseconds: 400)
cropAspectRatio 裁剪框的宽高比 null(无宽高比)
initialCropAspectRatio 初始化的裁剪框的宽高比 null(custom: 填充满图片原始宽高比)
initCropRectType 剪切框的初始化类型(根据图片初始化区域或者图片的 layout 区域) imageRect
hitTestBehavior 设置hittest的行为 HitTestBehavior.deferToChild

裁剪框的宽高比

这是一个 double 类型,你可以自定义裁剪框的宽高比。 如果为 null,那就没有宽高比限制。 如果小于等于 0,宽高比等于图片的宽高比。 下面是一些定义好了的宽高比

class CropAspectRatios {
  /// no aspect ratio for crop
  static const double custom = null;

  /// the same as aspect ratio of image
  /// [cropAspectRatio] is not more than 0.0, it's original
  static const double original = 0.0;

  /// ratio of width and height is 1 : 1
  static const double ratio1_1 = 1.0;

  /// ratio of width and height is 3 : 4
  static const double ratio3_4 = 3.0 / 4.0;

  /// ratio of width and height is 4 : 3
  static const double ratio4_3 = 4.0 / 3.0;

  /// ratio of width and height is 9 : 16
  static const double ratio9_16 = 9.0 / 16.0;

  /// ratio of width and height is 16 : 9
  static const double ratio16_9 = 16.0 / 9.0;
}

裁剪图层 Painter

你现在可以通过覆写 [EditorConfig.editorCropLayerPainter] 里面的方法来自定裁剪图层.

class EditorCropLayerPainter {
  const EditorCropLayerPainter();
  void paint(Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {
    paintMask(canvas, size, painter);
    paintLines(canvas, size, painter);
    paintCorners(canvas, size, painter);
  }

  /// draw crop layer corners
  void paintCorners(
      Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {
  }

  /// draw crop layer lines
  void paintMask(
      Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {
  }
  

  /// draw crop layer lines
  void paintLines(
      Canvas canvas, Size size, ExtendedImageCropLayerPainter painter) {
  } 
}

旋转,翻转,重置

  • 定义 key,以方便操作 ExtendedImageEditorState

final GlobalKey<ExtendedImageEditorState> editorKey =GlobalKey<ExtendedImageEditorState>();

  • 顺时针旋转 90°

editorKey.currentState.rotate(right: true);

  • 逆时针旋转 90°

editorKey.currentState.rotate(right: false);

  • 翻转(镜像)

editorKey.currentState.flip();

  • 重置

editorKey.currentState.reset();

裁剪数据

使用 dart 库(稳定)

  • 添加 Image 库到 pubspec.yaml, 它是用来裁剪/旋转/翻转图片数据的
dependencies:
  image: any
  • 从 ExtendedImageEditorState 中获取裁剪区域以及图片数据
  ///crop rect base on raw image
  final Rect cropRect = state.getCropRect();

  var data = state.rawImageData;
  • 将 flutter 的图片数据转换为 image 库的数据
  /// it costs much time and blocks ui.
  //Image src = decodeImage(data);

  /// it will not block ui with using isolate.
  //Image src = await compute(decodeImage, data);
  //Image src = await isolateDecodeImage(data);
  final lb = await loadBalancer;
  Image src = await lb.run<Image, List<int>>(decodeImage, data);
  • 翻转,旋转,裁剪数据
  //相机拍照的图片带有旋转,处理之前需要去掉
  src = bakeOrientation(src);

  if (editAction.needCrop)
    src = copyCrop(src, cropRect.left.toInt(), cropRect.top.toInt(),
        cropRect.width.toInt(), cropRect.height.toInt());

  if (editAction.needFlip) {
    Flip mode;
    if (editAction.flipY && editAction.flipX) {
      mode = Flip.both;
    } else if (editAction.flipY) {
      mode = Flip.horizontal;
    } else if (editAction.flipX) {
      mode = Flip.vertical;
    }
    src = flip(src, mode);
  }

  if (editAction.hasRotateAngle) src = copyRotate(src, editAction.rotateAngle);
  • 将数据转为为图片的元数据

获取到的将是图片的元数据,你可以使用它来保存或者其他的一些用途

  /// you can encode your image
  ///
  /// it costs much time and blocks ui.
  //var fileData = encodeJpg(src);

  /// it will not block ui with using isolate.
  //var fileData = await compute(encodeJpg, src);
  //var fileData = await isolateEncodeImage(src);
  var fileData = await lb.run<List<int>, Image>(encodeJpg, src);

使用原生库(快速)

  • 添加 ImageEditor 库到 pubspec.yaml, 它是用来裁剪/旋转/翻转图片数据的。
dependencies:
  image_editor: any
  • 从 ExtendedImageEditorState 中获取裁剪区域以及图片数据
  ///crop rect base on raw image
  final Rect cropRect = state.getCropRect();

  final img = state.rawImageData;
  • 准备裁剪选项
  final rotateAngle = action.rotateAngle.toInt();
  final flipHorizontal = action.flipY;
  final flipVertical = action.flipX;

  ImageEditorOption option = ImageEditorOption();

  if (action.needCrop) option.addOption(ClipOption.fromRect(cropRect));

  if (action.needFlip)
    option.addOption(
        FlipOption(horizontal: flipHorizontal, vertical: flipVertical));

  if (action.hasRotateAngle) option.addOption(RotateOption(rotateAngle));
  • 使用 editImage 方法进行裁剪

获取到的将是图片的元数据,你可以使用它来保存或者其他的一些用途

  final result = await ImageEditor.editImage(
    image: img,
    imageEditorOption: option,
  );

more detail

图片浏览

支持跟微信/掘金一样的图片查看效果

ExtendedImageGesturePageView 跟官方 PageView 一样的使用,不同的是,它避免了跟缩放拖拽手势冲突

支持缓存手势的状态,就是说你缩放了图片,然后下一个,再回到之前的图片,图片的缩放状态可以保存, 如果你缓存了手势,记住在合适的时候使用 clearGestureDetailsCache()清除掉,比如页面销毁的时候

img

ExtendedImageGesturePageView

parameter description default
canMovePage 是否滑动页面.有些场景如果 Scale 大于 1.0,并不想滑动页面,可以返回 false true

GestureConfig

参数 描述 默认
cacheGesture 是否缓存手势状态,可用于 ExtendedImageGesturePageView 中保留状态,使用 clearGestureDetailsCache 方法清除 false
inPageView 是否使用 ExtendedImageGesturePageView 展示图片 false
ExtendedImageGesturePageView.builder(
  itemBuilder: (BuildContext context, int index) {
    var item = widget.pics[index].picUrl;
    Widget image = ExtendedImage.network(
      item,
      fit: BoxFit.contain,
      mode: ExtendedImageMode.gesture,
      gestureConfig: GestureConfig(
        inPageView: true, initialScale: 1.0,
        //you can cache gesture state even though page view page change.
        //remember call clearGestureDetailsCache() method at the right time.(for example,this page dispose)
        cacheGesture: false
      ),
    );
    image = Container(
      child: image,
      padding: EdgeInsets.all(5.0),
    );
    if (index == currentIndex) {
      return Hero(
        tag: item + index.toString(),
        child: image,
      );
    } else {
      return image;
    }
  },
  itemCount: widget.pics.length,
  onPageChanged: (int index) {
    currentIndex = index;
    rebuild.add(index);
  },
  controller: PageController(
    initialPage: currentIndex,
  ),
  scrollDirection: Axis.horizontal,
),

滑动退出页面

支持微信掘金滑动退出页面的效果

img

首先开启滑动退出页面效果

ExtendedImage

parameter description default
enableSlideOutPage 是否开启滑动退出页面效果 false
heroBuilderForSlidingPage 滑动退出页面的 transform 必须作用在 Hero 上面,这样在退出页面的时候,Hero 动画才不会奇怪 null

把你的页面用 ExtendedImageSlidePage 包一下

注意:onSlidingPage 回调,你可以使用它来设置滑动页面的时候,页面上其他元素的状态。但是注意别直接使用 setState 来刷新,因为这样会导致 ExtendedImage 的状态重置掉,你应该只通知需要刷新的 Widgets 进行刷新

    return ExtendedImageSlidePage(
      child: result,
      slideAxis: SlideAxis.both,
      slideType: SlideType.onlyImage,
      onSlidingPage: (state) {
        ///you can change other widgets' state on page as you want
        ///base on offset/isSliding etc
        //var offset= state.offset;
        var showSwiper = !state.isSliding;
        if (showSwiper != _showSwiper) {
          // do not setState directly here, the image state will change,
          // you should only notify the widgets which are needed to change
          // setState(() {
          // _showSwiper = showSwiper;
          // });

          _showSwiper = showSwiper;
          rebuildSwiper.add(_showSwiper);
        }
      },
    );

ExtendedImageGesturePage 的参数

parameter description default
child 需要包裹的页面 -
slidePageBackgroundHandler 在滑动页面的时候根据 Offset 自定义整个页面的背景色 defaultSlidePageBackgroundHandler
slideScaleHandler 在滑动页面的时候根据 Offset 自定义整个页面的缩放值 defaultSlideScaleHandler
slideEndHandler 滑动页面结束的时候计算是否需要 pop 页面 defaultSlideEndHandler
slideAxis 滑动页面的方向(both,horizontal,vertical),掘金是 vertical,微信是 Both both
resetPageDuration 滑动结束,如果不 pop 页面,整个页面回弹动画的时间 milliseconds: 500
slideType 滑动整个页面还是只是图片(wholePage/onlyImage) SlideType.onlyImage
onSlidingPage 滑动页面的回调,你可以在这里改变页面上其他元素的状态 -
slideOffsetHandler 在滑动页面的时候自定义 Offset -

下面是默认实现,你也可以根据你的喜好,来定义属于自己方式

Color defaultSlidePageBackgroundHandler(
    {Offset offset, Size pageSize, Color color, SlideAxis pageGestureAxis}) {
  double opacity = 0.0;
  if (pageGestureAxis == SlideAxis.both) {
    opacity = offset.distance /
        (Offset(pageSize.width, pageSize.height).distance / 2.0);
  } else if (pageGestureAxis == SlideAxis.horizontal) {
    opacity = offset.dx.abs() / (pageSize.width / 2.0);
  } else if (pageGestureAxis == SlideAxis.vertical) {
    opacity = offset.dy.abs() / (pageSize.height / 2.0);
  }
  return color.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
}

bool defaultSlideEndHandler(
    {Offset offset, Size pageSize, SlideAxis pageGestureAxis}) {
  if (pageGestureAxis == SlideAxis.both) {
    return offset.distance >
        Offset(pageSize.width, pageSize.height).distance / 3.5;
  } else if (pageGestureAxis == SlideAxis.horizontal) {
    return offset.dx.abs() > pageSize.width / 3.5;
  } else if (pageGestureAxis == SlideAxis.vertical) {
    return offset.dy.abs() > pageSize.height / 3.5;
  }
  return true;
}

double defaultSlideScaleHandler(
    {Offset offset, Size pageSize, SlideAxis pageGestureAxis}) {
  double scale = 0.0;
  if (pageGestureAxis == SlideAxis.both) {
    scale = offset.distance / Offset(pageSize.width, pageSize.height).distance;
  } else if (pageGestureAxis == SlideAxis.horizontal) {
    scale = offset.dx.abs() / (pageSize.width / 2.0);
  } else if (pageGestureAxis == SlideAxis.vertical) {
    scale = offset.dy.abs() / (pageSize.height / 2.0);
  }
  return max(1.0 - scale, 0.8);
}

确保你的页面是透明背景的

如果你设置 slideType =SlideType.onlyImage, 请确保的你页面是透明的,毕竟没法操控你页面上的颜色

Push 一个透明的页面

这里我把官方的 MaterialPageRoute 和 CupertinoPageRoute 拷贝出来了, 改为 TransparentMaterialPageRoute/TransparentCupertinoPageRoute,因为它们的 opaque 不能设置为 false

  Navigator.push(
    context,
    Platform.isAndroid
        ? TransparentMaterialPageRoute(builder: (_) => page)
        : TransparentCupertinoPageRoute(builder: (_) => page),
  );

滑动退出页面相关代码演示 1

滑动退出页面相关代码演示 2

Border BorderRadius Shape

ExtendedImage

参数 描述 默认
border 跟官方的含义一样,你可以通过它设置边框 -
borderRadius 跟官方的含义一样,你可以通过它设置圆角
shape 跟官方的含义一样,你可以通过它设置裁剪(矩形和圆) -
ExtendedImage.network(
  url,
  width: ScreenUtil.instance.setWidth(400),
  height: ScreenUtil.instance.setWidth(400),
  fit: BoxFit.fill,
  cache: true,
  border: Border.all(color: Colors.red, width: 1.0),
  shape: boxShape,
  borderRadius: BorderRadius.all(Radius.circular(30.0)),
),

img

清除缓存和保存

清除缓存

清除本地缓存,可以调用 clearDiskCachedImages 方法

// Clear the disk cache directory then return if it succeed.
///  <param name="duration">timespan to compute whether file has expired or not</param>
Future<bool> clearDiskCachedImages({Duration duration})

根据某一个 url 清除缓存, 可以调用 clearDiskCachedImage 方法.

/// clear the disk cache image then return if it succeed.
///  <param name="url">clear specific one</param>
Future<bool> clearDiskCachedImage(String url) async {

根据 url 获取缓存图片文件

Future<File> getCachedImageFile(String url) async {

清除内存缓存,可以调用 clearMemoryImageCache 方法

///clear all of image in memory
 clearMemoryImageCache();

/// get ImageCache
 getMemoryImageCache() ;

保存网络图片

这是一个例子,使用到image_picker_saver 插件,ExtendedImage 做的只是提供网络图片的数据源

///save network image to photo
Future<bool> saveNetworkImageToPhoto(String url, {bool useCache: true}) async {
  var data = await getNetworkImageData(url, useCache: useCache);
  var filePath = await ImagePickerSaver.saveFile(fileData: data);
  return filePath != null && filePath != "";
}

显示裁剪图片

img

你可以通过 ExtendedRawImage(可以在状态回调的时候使用),sourceRect 是你想要显示图片的哪一部分,这个在各个 app 里面应该是比较常见的操作

ExtendedRawImage(
  image: image,
  width: num400,
  height: num300,
  fit: BoxFit.fill,
  sourceRect: Rect.fromLTWH(
      (image.width - width) / 2.0, 0.0, width, image.height.toDouble()),
)

绘制

img

提供了 BeforePaintImage and AfterPaintImage 两个回调, 这样你就能绘制你想要的东西或者进图片进行 Clip。

ExtendedImage

parameter description default
beforePaintImage 在绘制图片之前 -
afterPaintImage 在绘制图片之后 -
  ExtendedImage.network(
    url,
    width: ScreenUtil.instance.setWidth(400),
    height: ScreenUtil.instance.setWidth(400),
    fit: BoxFit.fill,
    cache: true,
    beforePaintImage: (Canvas canvas, Rect rect, ui.Image image) {
      if (paintType == PaintType.ClipHeart) {
        if (!rect.isEmpty) {
          canvas.save();
          canvas.clipPath(clipheart(rect, canvas));
        }
      }
      return false;
    },
    afterPaintImage: (Canvas canvas, Rect rect, ui.Image image) {
      if (paintType == PaintType.ClipHeart) {
        if (!rect.isEmpty) canvas.restore();
      } else if (paintType == PaintType.PaintHeart) {
        canvas.drawPath(
            clipheart(rect, canvas),
            Paint()
              ..colorFilter =
                  ColorFilter.mode(Color(0x55ea5504), BlendMode.srcIn)
              ..isAntiAlias = false
              ..filterQuality = FilterQuality.low);
      }
    },
  );

在例子中可以看到把图片 Clip 成了一个桃心,你也可以根据你的要求,做出不同的 Clip 绘制例子 下拉刷新头当中,也使用了这个技巧

Notch

设置图片初始化的 Insets,不会影响放大。

ExtendedImage

parameter description default
layoutInsets 设置图片初始化时候的边距 EdgeInsets.zero
  ExtendedImage.network(
    url,
    fit: BoxFit.contain,
    layoutInsets: MediaQuery.of(context).padding
  );

内存使用

现在你可以通过下面设置来减少图片内存的占用.

  • ExtendedResizeImage
parameter description default
[ExtendedResizeImage.compressionRatio] 图片压缩率,大于0小于1 null
[ExtendedResizeImage.maxBytes] 图片的大小的最大值. 默认值为 50KB. 这是图片实际的的大小,而不是解码之后的大小 50 << 10
[ExtendedResizeImage.width]/[ExtendedResizeImage.height] 宽和高用于decode和缓存. 跟官方的[ResizeImage]一致。 null
    ExtendedImage.network(
      'imageUrl',  
      compressionRatio: 0.1,
      maxBytes: null,
      cacheWidth: null,
      cacheHeight: null,  
    )

    ExtendedImage(
      image: ExtendedResizeImage(
        ExtendedNetworkImageProvider(
          'imageUrl',  
        ),
        compressionRatio: 0.1,
        maxBytes: null,
        width: null,
        height: null,
      ),
    )
  • clearMemoryCacheWhenDispose
parameter description default
clearMemoryCacheWhenDispose 在Flutter 2.0之后也许不会起作用, 因为没法在图片没有完成之前释放掉(fluttercandies#317). 现在只会释放已完成的图片资源. false
   ExtendedImage.network(
     'imageUrl',     
     clearMemoryCacheWhenDispose: true,
   )
  • imageCacheName
parameter description default
imageCacheName 你可以指定一个 ImageCache 来缓存一些图片。这样你可以一起处理它们,不会影响其他的图片缓存. null
   ExtendedImage.network(
     'imageUrl',  
     imageCacheName: 'MemoryUsage',
   )
     
  /// clear when this page is disposed   
  @override
  void dispose() {
    // clear ImageCache which named 'MemoryUsage'
    clearMemoryImageCache(imageCacheName);
    super.dispose();
  }   

其他 APIs

ExtendedImage

参数 描述 默认
enableMemoryCache 是否缓存到内存 true
clearMemoryCacheIfFailed 如果图片加载失败,是否清掉内存缓存 true
clearMemoryCacheWhenDispose 如果图片从 tree 中移除,是否清掉内存缓存 false