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

作者: ZhengYaWei | 来源:发表于2017-02-11 16:52 被阅读1041次

    之前利用过零零散散的时间封装了一个属于自己图片浏览器框架,代码量总共也就五六百行吧,昨天刚刚封装完成,今天想拿来分享总结一下。写这篇文章的最主要目的不是想宣传自己的框架,重点是想整理一下思路,并且告诉看了此文章的人,如何也能封装一个自己的图片浏览器框架。自我感觉封装的还算良好,支持长图显示、捏合手势、单双击手势、支持动画效果、支持长按保存图片。后期再给补充一个显示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中有专门封装的一个类。之前也写过一篇文章关于进度加载的控件的实现,可以参考我之前写的这篇文章:http://www.jianshu.com/p/580298595e10

    说一下图片浏览器的层次结构,实际上是有两层UIScrollView,首先每个imageView都有一个一一对应的UIScrollView父视图。然后多张图片的连续现实也是借助UIScrollView实现的。另外图片浏览器一般是放置在UIWindow上显示的,这样管理起来更科学,实现起来动画效果也比较方便。具体实现代码可以看 [photoBrowser show];这个方法。

    看看我在封装这个框架中一步一步是怎么做的:
    1、首先是封装了一个加载动画的ZWWaitingView
    2、之后封装了一个继承于UIImageView的单张图片显示的类ZWBrowserImageView,这个类主要是负责单张图片的显示,并且添加有捏合手势控制图片大小;上面效果图中单张图片测试按钮点击进入,就是对应这一步的成果。
    3、最后封装了一个继承于UIView的ZWPhotoBrowser,然后创建一个scrollView对象,将对应的ZWBrowserImageView实例对象依次放置到scrollView上,动画效果、单双击手势、保存图片、图片张数显示等实现逻辑都放置在这个类中。

    ==================第一步骤========================
    上面的第一步就不多说了,具体请看我之前写的文章。http://www.jianshu.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];
    }
    

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

    相关文章

      网友评论

      • 阿棍儿_Leon:亚伟呀,你这么能写,以前怎么不分享
        ZhengYaWei:@阿棍儿Leon 额,以前写的都比较垃圾啊。最近半年多写的文章尽可能保证质量!粉丝量也是最近半年多涨上来的:smile:
      • PGOne爱吃饺子:楼主啊 ,想给你提点意见都找不到你啊
      • PGOne爱吃饺子:楼主,在不在啊
        PGOne爱吃饺子:@PGOne爱吃饺子 在展示image的地方有几处要优化的地方
        PGOne爱吃饺子:@ZhengYaWei 你这个demo里面有个可以优化的地方,就是图片加载完成的时候,在展示图片的时候,因为占位图的问题,展示图片的时候可能会出现白色的一闪而过的现象。
        ZhengYaWei:@PGOne爱吃饺子 这个文章好久之前写的了,怎么?
      • PGOne爱吃饺子:楼主你好,看了你写的源码有个问题很是不明白
      • c44c0bf3f747:造轮子
      • chenzhy:大神,那个ZWBrowserImageView.m文件layoutSubviews方法中的if (self.bounds.size.width * (imageSize.height / imageSize.width) > self.bounds.size.height) {...}判断条件是判断什么的?
        chenzhy:@ZhengYaWei 刚想问你是不是判断长图的,没想到那么快就回复了....对我来说你就很厉害啦:smile:
        ZhengYaWei:@Z勇 不是什么大神 只是没事会研究一些其他人的代码,然后变为己用而已
        ZhengYaWei:@Z勇 图片浏览器是加上是有考虑长图显示问题的。这个判断就是用于判断是否为长图,当时长图的时候就直接用_scroll来显示,这样的话就可以上下滑动。你可以想想成imageSize.width和self.bounds.size.width 相等,左右两边的条件判断也就是imageSize.height>self.bounds.size.height (也就是说明是长图)。相信这个解释的应该比较清楚了 😀
      • 知晓程序:你好!我们是爱范儿旗下专注于小程序生态的公众号知晓程序(微信号 zxcx0101)。我们很赞赏你的文章,希望能获得转载授权。授权后,你的文章将会在知晓程序社区(minapp.com)、爱范儿、AppSo 等渠道发布,我们会注明来源和作者姓名。

        非常感谢~~~
        jkkkg:@Z勇 我回头看看吧,现在不在公司。身边没电脑!
        chenzhy:大神你好,我看你的github上的源码不见了,是没放上去吗

      本文标题:如何封装一个自己图片浏览器框架

      本文链接:https://www.haomeiwen.com/subject/gkkoittx.html