extended_image 0.4.1 extended_image: ^0.4.1 copied to clipboard
extended official image which support placeholder(loading)/ failed state,cache network,zoom pan image,photo view,slide out page,crop,save,paint etc.
extended_image #
extended official image which support placeholder(loading)/ failed state,cache network,zoom pan image,photo view,slide out page,crop,save,paint etc.
Table of contents #
- extended_image
Cache Network #
simple use #
you can use ExtendedImage.network as same as official image.
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,
)
use 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();
parameter | description | default |
---|---|---|
url | The URL from which the image will be fetched. | required |
scale | The scale to place in the [ImageInfo] object of the image. | 1.0 |
headers | The HTTP headers that will be used with [HttpClient.get] to fetch image from network. | - |
cache | whether cache image to local | false |
retries | the time to retry to request | 3 |
timeLimit | time limit to request image | - |
timeRetry | the time duration to retry to request | milliseconds: 100 |
cancelToken | token to cancel network request | CancellationToken() |
Load State #
Extended Image provide 3 states(loading,completed,failed), you can define your state widget with loadStateChanged call back.
loadStateChanged is not only for network, if your image need long time to load, you can set enableLoadState(default value is ture for network and others are false) to ture
/// 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(LoadStateChanged call back)
parameter/method | description | default |
---|---|---|
extendedImageInfo | image info | - |
extendedImageLoadState | LoadState(loading,completed,failed) | - |
returnLoadStateChangedWidget | if this is ture, return widget which from LoadStateChanged fucntion immediately(width/height/gesture/border/shape etc, will not effect on it) | - |
imageProvider | ImageProvider | - |
invertColors | invertColors | - |
imageStreamKey | key of image | - |
reLoadImage() | if image load failed,you can reload image by call it | - |
abstract class ExtendedImageState {
void reLoadImage();
ImageInfo get extendedImageInfo;
LoadState get extendedImageLoadState;
///return widget which from LoadStateChanged fucntion immediately
bool returnLoadStateChangedWidget;
ImageProvider get imageProvider;
bool get invertColors;
Object get imageStreamKey;
}
demo code #
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;
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;
}
},
)
Zoom Pan #
ExtendedImage
parameter | description | default |
---|---|---|
mode | image mode (none,gestrue) | none |
gestureConfig | config for image gesture | - |
onDoubleTap | call back of double tap under ExtendedImageMode.Gesture | - |
GestureConfig
parameter | description | default |
---|---|---|
minScale | min scale | 0.8 |
animationMinScale | the min scale for zooming then animation back to minScale when scale end | minScale _ 0.8 |
maxScale | max scale | 5.0 |
animationMaxScale | the max scale for zooming then animation back to maxScale when scale end | maxScale _ 1.2 |
speed | speed for zoom/pan | 1.0 |
inertialSpeed | inerial speed for zoom/pan | 100 |
cacheGesture | save Gesture state (for example in page view, so that the state will not change when scroll back),remember clearGestureDetailsCache at right time | false |
inPageView | whether in ExtendedImageGesturePageView | false |
ExtendedImage.network(
imageTestUrl,
fit: BoxFit.contain,
//enableLoadState: false,
mode: ExtendedImageMode.Gesture,
gestureConfig: GestureConfig(
minScale: 0.9,
animationMinScale: 0.7,
maxScale: 3.0,
animationMaxScale: 3.5,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
),
)
double tap animation #
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion.
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();
},
Photo View #
ExtendedImageGesturePageView is the same as PageView and it's made for show zoom/pan image.
if you have cache the gesture, remember call clearGestureDetailsCache() method at the right time.(for example,page view page is disposed)
GestureConfig
parameter | description | default |
---|---|---|
cacheGesture | save Gesture state (for example in page view, so that the state will not change when scroll back),remember clearGestureDetailsCache at right time | false |
inPageView | whether in 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,
),
Slide Out Page #
Extended Image support to slide out page as WeChat.
include your page in ExtendedImageSlidePage #
var page = ExtendedImageSlidePage(
child: PicSwiper(
index,
listSourceRepository
.map<PicSwiperItem>(
(f) => PicSwiperItem(f.imageUrl, des: f.title))
.toList(),
),
slideAxis: SlideAxis.both,
slideType: SlideType.onlyImage,
);
ExtendedImageGesturePage
parameter | description | default |
---|---|---|
child | The [child] contained by the ExtendedImageGesturePage. | - |
slidePageBackgroundHandler | build background when slide page | defaultSlidePageBackgroundHandler |
slideScaleHandler | custom scale of page when slide page | defaultSlideScaleHandler |
slideEndHandler | call back of slide end,decide whether pop page | defaultSlideEndHandler |
slideAxis | axis of slide(both,horizontal,vertical) | SlideAxis.both |
resetPageDuration | reset page position when slide end(not pop page) | milliseconds: 500 |
slideType | slide whole page or only image | SlideType.onlyImage |
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);
}
make sure your page background is transparent #
if you use ExtendedImageSlidePage and slideType =SlideType.onlyImage, make sure your page background is transparent
push with transparent page route #
you should push page with TransparentMaterialPageRoute/TransparentCupertinoPageRoute
Navigator.push(
context,
Platform.isAndroid
? TransparentMaterialPageRoute(builder: (_) => page)
: TransparentCupertinoPageRoute(builder: (_) => page),
);
Border BorderRadius Shape #
ExtendedImage
parameter | description | default |
---|---|---|
border | BoxShape.circle and BoxShape.rectangle,If this is [BoxShape.circle] then [borderRadius] is ignored. | - |
borderRadius | If non-null, the corners of this box are rounded by this [BorderRadius].,Applies only to boxes with rectangular shapes; ignored if [shape] is not [BoxShape.rectangle]. | - |
shape | BoxShape.circle and BoxShape.rectangle,If this is [BoxShape.circle] then [borderRadius] is ignored. | - |
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)),
),
Clear Save #
clear #
to clear disk cached , call clearDiskCachedImages method.
// 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})
to clear memory cache , call clearMemoryImageCache method.
///clear all of image in memory
clearMemoryImageCache();
/// get ImageCache
getMemoryImageCache() ;
save network #
call saveNetworkImageToPhoto and save image with image_picker_saver
///save netwrok 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 != "";
}
Crop #
get your raw image by [Load State](#Load State), and crop image by setting soureRect.
ExtendedRawImage soureRect is which you want to show image rect.
ExtendedRawImage(
image: image,
width: num400,
height: num300,
fit: BoxFit.fill,
soucreRect: Rect.fromLTWH(
(image.width - width) / 2.0, 0.0, width, image.height.toDouble()),
)
Paint #
provide BeforePaintImage and AfterPaintImage callback, you will have the chance to paint things you want.
ExtendedImage
parameter | description | default |
---|---|---|
beforePaintImage | you can paint anything if you want before paint image. | - |
afterPaintImage | you can paint anything if you want after paint image. | - |
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);
}
},
);
see paint image demo and push to refresh header which is used in crop image demo
Other APIs #
ExtendedImage
parameter | description | default |
---|---|---|
enableMemoryCache | whether cache in PaintingBinding.instance.imageCache) | true |
clearMemoryCacheIfFailed | when failed to load image, whether clear memory cache.if ture, image will reload in next time. | true |