如何封装一个自己图片浏览器框架

之前利用过零零散散的时间封装了一个属于自己图片浏览器框架,代码量总共也就五六百行吧,昨天刚刚封装完成,今天想拿来分享总结一下。写这篇文章的最主要目的不是想宣传自己的框架,重点是想整理一下思路,并且告诉看了此文章的人,如何也能封装一个自己的图片浏览器框架。自我感觉封装的还算良好,支持长图显示、捏合手势、单双击手势、支持动画效果、支持长按保存图片。后期再给补充一个显示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];
}

好好研究下,不到一天绝对能封装出来一个自己的图片浏览器框架。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,800评论 25 707
  • 王建军 生活有时候是如此简单,一杯酒加一点小菜就够了。微醺的世界里,一颗灵魂是自由的。从川地走出去的李白,他的名字...
    东营王建军阅读 299评论 4 6
  • 文|般若芙殇 随着故事书最后一段的结束,终于在儿子枕边,把《狼来了》这个故事说完,而最重要的寓意,也不忘用引导性的...
    李诗民阅读 679评论 2 11
  • 不知为何,忽然想到了珍惜,二十多年的人生里,我失去过、得到过、放弃过、后悔过。唯有这份友谊还在那里,永远在那里。 ...
    三四木阅读 149评论 0 1