之前利用过零零散散的时间封装了一个属于自己图片浏览器框架,代码量总共也就五六百行吧,昨天刚刚封装完成,今天想拿来分享总结一下。写这篇文章的最主要目的不是想宣传自己的框架,重点是想整理一下思路,并且告诉看了此文章的人,如何也能封装一个自己的图片浏览器框架。自我感觉封装的还算良好,支持长图显示、捏合手势、单双击手势、支持动画效果、支持长按保存图片。后期再给补充一个显示gif图片的动能,之后再抽空发布到pods上。效果动态图如下:
代码量没有多少,一旦学会了封装自己的框架,以后网上的一些第三方完全可以不用,并且还能根据项目需求改变不同的样式,想想都挺激动地。😀😀😀如果肯结合这篇文章花上半天到一天的时间去研究一下,基本上也能封装一套自己的图片浏览器框架。
看这篇文章一定要结合源码看,因为为了控制文章篇幅,我只说明了封装过程的总体思路和中间的一些注意点。源码下载地址:https://github.com/ZhengYaWei1992/ZWPhotoBrowser
还是老样子,先看一下如何使用,对外提供的接口是什么样。说明:下面的第二个代理方法是必须要实现的,主要原因是在动画效果中的时候,要获取到上一界面的原本图片,用这个图片做动画效果。当imageView的容器视图为一般的UIView的时候没有问题。但是当容器视图为UICollectionView的时候,通过sourceImagesContainerView还要做额外判断,情况不定。所以通过代理更为合理!这里考虑还是比较全面的,考虑到容器视图为collectionView情况。
- (void)ivTap:(UITapGestureRecognizer *)tap{
ZWPhotoBrowser *photoBrowser = [[ZWPhotoBrowser alloc]init];
photoBrowser.delegate = self;
photoBrowser.currentImageIndex = tap.view.tag;
photoBrowser.imageCount = self.smallPicsUrls.count;
photoBrowser.sourceImagesContainerView = _containerView;
photoBrowser.placeholderImage = [UIImage imageNamed:@"placeholder"];
[photoBrowser show];
}
#pragma mark -ZWPhotoBrowserDelegate
/**
返回高清图像的URL,如果没有写这个方法,则显示小图像
*/
- (NSURL *)photoBrowser:(ZWPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index{
return [NSURL URLWithString:self.bigPicsUrls[index]];
}
/**
获取imageView上的原本小图 这是必须实现的代理方法
主要是为了设置动画效果
*/
- (UIImage *)photoBrowser:(ZWPhotoBrowser *)browser smallImageForIndex:(NSInteger)index{
UIImageView *imageView = self.containerView.subviews[index];
return imageView.image;
}
先在这里补充一个小知识点,上面效果图的大图加载动画是自己写的,主要通过绘图实现的,demo中有专门封装的一个类。之前也写过一篇文章关于进度加载的控件的实现,可以参考我之前写的这篇文章://www.greatytc.com/p/580298595e10
说一下图片浏览器的层次结构,实际上是有两层UIScrollView,首先每个imageView都有一个一一对应的UIScrollView父视图。然后多张图片的连续现实也是借助UIScrollView实现的。另外图片浏览器一般是放置在UIWindow上显示的,这样管理起来更科学,实现起来动画效果也比较方便。具体实现代码可以看 [photoBrowser show];这个方法。
看看我在封装这个框架中一步一步是怎么做的:
1、首先是封装了一个加载动画的ZWWaitingView
2、之后封装了一个继承于UIImageView的单张图片显示的类ZWBrowserImageView,这个类主要是负责单张图片的显示,并且添加有捏合手势控制图片大小;上面效果图中单张图片测试按钮点击进入,就是对应这一步的成果。
3、最后封装了一个继承于UIView的ZWPhotoBrowser,然后创建一个scrollView对象,将对应的ZWBrowserImageView实例对象依次放置到scrollView上,动画效果、单双击手势、保存图片、图片张数显示等实现逻辑都放置在这个类中。
==================第一步骤========================
上面的第一步就不多说了,具体请看我之前写的文章。//www.greatytc.com/p/580298595e10
==================第二步骤========================
看看第二步的实现吧。实现单张图片的显示的外部接口调用代码。
ZWBrowserImageView *iv = [[ZWBrowserImageView alloc]initWithFrame:CGRectMake(0, 80, self.view.frame.size.width, self.view.frame.size.height - 80 - 49)];
//设置图片内容
[iv setImageWithURL:[NSURL URLWithString:@"http://weixintest.ihk.cn/ihkwx_upload/commentPic/20160519/14636417292422.jpg"] placeholderImage:[UIImage imageNamed:@"placholder"]];
[self.view addSubview:iv];
ZWBrowserImageView这个类中,针对于缩放状态和非缩放状态分别创建了scroll、scrollImageView和zoomingScrollView、zoomingImageView。这样管理起来更方便,实现显示的时候通过视图层次结构的遮挡,视觉上形成具体效果就好。另外只有借助scrollView才能更好的实现捏合手势。
设置imageView图片的实现代码,包含进度加载视图的逻辑代码。
#pragma mark - 设置imageView内容
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{
//1.添加加载视图
ZWWaitingView *waitingView = [[ZWWaitingView alloc]init];
waitingView.bounds = CGRectMake(0, 0, 80, 80);
waitingView.clipsToBounds = YES;
waitingView.layer.cornerRadius = 40;
//默认饼形加载视图
waitingView.mode = ZWWaitingViewModePieDiagram;
_waitingView = waitingView;
[self addSubview:waitingView];
// __weak typeof(self) imageViewWeak = self;
__weak ZWBrowserImageView *imageViewWeak = self;
[self sd_setImageWithURL:url placeholderImage:placeholder options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
//设置进度,调用setProgress方法
imageViewWeak.progress = (CGFloat)receivedSize / expectedSize;
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//加载完成移除waitingView
[_waitingView removeFromSuperview];
if(error){//图片加载失败
UILabel *label = [[UILabel alloc] init];
label.bounds = CGRectMake(0, 0, 160, 30);
label.center = CGPointMake(imageViewWeak.bounds.size.width * 0.5, imageViewWeak.bounds.size.height * 0.5);
label.text = @"图片加载失败";
label.font = [UIFont systemFontOfSize:16];
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
label.layer.cornerRadius = 5;
label.clipsToBounds = YES;
label.textAlignment = NSTextAlignmentCenter;
[imageViewWeak addSubview:label];
}else{//图片加载成功
_scrollImageView.image = image;
//加载成功,调用layoutSubviews方法
[_scrollImageView setNeedsDisplay];
}
}];
}
单张图片捏合手势实现逻辑。
#pragma mark -手势捏合事件
- (void)zoomImage:(UIPinchGestureRecognizer *)recognizer{
[self prepareForImageViewScaling];
//设置缩放比例
CGFloat scale = recognizer.scale;
CGFloat temp = _totalScale + (scale - 1);
[self setTotalScale:temp];
recognizer.scale = 1.0;
}
//设置缩放比例
- (void)setTotalScale:(CGFloat)totalScale{
//最大缩放 2倍,最小0.5倍
if ((_totalScale < 0.5 && totalScale < _totalScale) || (_totalScale > 2.0 && totalScale > _totalScale)){
return;
}
[self zoomWithScale:totalScale];
}
- (void)zoomWithScale:(CGFloat)scale{
_totalScale = scale;
_zoomingImageView.transform = CGAffineTransformMakeScale(scale, scale);
if (scale > 1) {//放大
CGFloat contentW = _zoomingImageView.frame.size.width;
//?????
CGFloat contentH = MAX(_zoomingImageView.frame.size.height, self.frame.size.height);
_zoomingImageView.center = CGPointMake(contentW * 0.5, contentH * 0.5);
_zoomingScrollView.contentSize = CGSizeMake(contentW, contentH);
CGPoint offset = _zoomingScrollView.contentOffset;
offset.x = (contentW - _zoomingScrollView.frame.size.width) * 0.5;
//开启了这句话,放大啊图片的时候会产生错位,体验效果不是很好
//offset.y = (contentH - _zoomingImageView.frame.size.height) * 0.5;
_zoomingScrollView.contentOffset = offset;
}else{//缩小
_zoomingScrollView.contentSize = _zoomingScrollView.frame.size;
//缩小时,同时设置内容填充边距为0
_zoomingScrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
_zoomingImageView.center = _zoomingScrollView.center;
}
}
双击手势事件实现逻辑代码。
#pragma mark - 对外提供的方法 public
//双击点击事件
- (void)doubleTapToZommWithScale:(CGFloat)scale{
[self prepareForImageViewScaling];
[UIView animateWithDuration:0.5 animations:^{
[self zoomWithScale:scale];
} completion:^(BOOL finished) {
//动画完成,如果scale为1,则移除zoomingScrollView和zoomingImageView
if (scale == 1) {
[self clear];
}
}];
}
上面代码只是第二步骤中的部分重点代码,借助scrollView可以实现长图的展示,双击放大缩小事件,捏合手势事件。这样一张图片就可以简单的展示出来。
==================第三步骤========================
主要借助scrollView合理的展示第二步中ZWBrowserImageView实例对象。在这一步中主要封装了ZWPhotoBrowser这个类,这个类中的几个注意点。
a、进入图片浏览器动画显示。
b、scrollView滑动到一定位置的时候再去加载图片,而不是一次全部将图片都加载完,否则当图片过多的时候,一次加载所有资源图片消耗内存非常大,严重时直接导致卡死。
c、在滚动到下一张图片的股哟城中,每张图片之间是有一定的间隙,这个要通过合理的布局才能显示。
d、在UIWindow上展示
***************a、动画效果*******************
代码中含有注释。
/**
展示的第一张图片要做特殊的处理
*/
- (void)showFirstImage{
UIView *sourceView = nil;
if ([self.sourceImagesContainerView isKindOfClass:UICollectionView.class]) {//容器视图为UICollectionView
UICollectionView *view = (UICollectionView *)self.sourceImagesContainerView;
NSIndexPath *path = [NSIndexPath indexPathForItem:self.currentImageIndex inSection:0];
sourceView = [view cellForItemAtIndexPath:path];
}else{//容器视图为一般的UIView
sourceView = self.sourceImagesContainerView.subviews[self.currentImageIndex];
}
CGRect rect = [self.sourceImagesContainerView convertRect:sourceView.frame toView:self];
UIImageView *tempView = [[UIImageView alloc] init];
tempView.image = [self smallImageForIndex:self.currentImageIndex];
[self addSubview:tempView];
CGRect targetTemp = [_scrollView.subviews[self.currentImageIndex] bounds];
tempView.frame = rect;
tempView.contentMode = [_scrollView.subviews[self.currentImageIndex] contentMode];
//执行动画之前先隐藏scrollView,动画执行完成后显示scrollView
_scrollView.hidden = YES;
[UIView animateWithDuration:ZWPhotoBrowserShowImageAnimationDuration animations:^{
tempView.center = self.center;
tempView.bounds = (CGRect){CGPointZero, targetTemp.size};
} completion:^(BOOL finished) {
_hasShowedFirstView = YES;
[tempView removeFromSuperview];
_scrollView.hidden = NO;
}];
}
***************b、滚动过程中加载资源*******************
#pragma mark - scrollview代理方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
int index = (scrollView.contentOffset.x + _scrollView.bounds.size.width * 0.5) / _scrollView.bounds.size.width;
// 有过缩放的图片在拖动一定距离后清除缩放
CGFloat margin = 150;
CGFloat x = scrollView.contentOffset.x;
if (x - index *self.bounds.size.width > margin || x - index * self.bounds.size.width < -margin) {
ZWBrowserImageView *imageView = _scrollView.subviews[index];
if (imageView.isScaled) {
[UIView animateWithDuration:0.5 animations:^{
imageView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[imageView eliminateScale];
}];
}
}
//设置UILabel显示内容
if (!_willDisappear) {
_indexLabel.text = [NSString stringWithFormat:@"%d/%ld", index + 1, (long)self.imageCount];
}
//滑动的时候加载下一张图片
[self setupImageOfImageViewForIndex:index];
}
***************c、合理的布局,显示分割线*******************
请看代码中的注释
/**
layoutSubviews方法
注意:为了让图片在滚动的时候显示间距的特殊布局做法。
scrollView宽度左右两边各加10 , scrollView的子视图左右两边也各加10,contentSize依然同常规的一样,然后scrollView按页滚动便实现中间有分割线的效果
*/
- (void)layoutSubviews{
[super layoutSubviews];
CGRect rect = self.bounds;
rect.size.width += ZWPhotoBrowserImageViewMargin * 2;
_scrollView.bounds = rect;
_scrollView.center = self.center;
CGFloat y = 0;
CGFloat w = _scrollView.frame.size.width - ZWPhotoBrowserImageViewMargin * 2;
CGFloat h = _scrollView.frame.size.height;
[_scrollView.subviews enumerateObjectsUsingBlock:^(__kindof ZWBrowserImageView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat x = ZWPhotoBrowserImageViewMargin + idx * (ZWPhotoBrowserImageViewMargin * 2 + w);
obj.frame = CGRectMake(x, y, w, h);
}];
_scrollView.contentSize = CGSizeMake(_scrollView.subviews.count * _scrollView.frame.size.width, 0);
_scrollView.contentOffset = CGPointMake(self.currentImageIndex * _scrollView.frame.size.width, 0);
//第一张图展示的时候,还要展示首张图片的动画效果
if(!_hasShowedFirstView){
[self showFirstImage];
}
_indexLabel.center = CGPointMake(self.bounds.size.width * 0.5, 35);
}
***************d、UIWindow上展示图片浏览器*******************
#pragma mark - public
//点击进入的时候是先显示window,再执行动画效果
- (void)show{
UIWindow *window = [UIApplication sharedApplication].keyWindow;
self.frame = window.bounds;
[window addObserver:self forKeyPath:@"frame" options:0 context:nil];
[window addSubview:self];
}
好好研究下,不到一天绝对能封装出来一个自己的图片浏览器框架。