iOS小项目iOS学习iOS学习开发

FJImageBrowser图片浏览器

2016-09-11  本文已影响308人  林大鹏

一.框架介绍

由于项目需求,然后也没有找到完全合适的第三方,所以自己写了一个图片浏览器,后来公司几个项目都用到了这个图片浏览器,为了适应各种产品需求,较之前添加了些功能,主要支持:

1.支持模仿微博的直接动画放大功能;
2.支持模仿微信的当下载中的时候,居中显示,下载完成放大,如果已经下载,直接动画放大。
3.支持本地图片和网络图片的混搭,比如说聊天页面有自己发送的本地图片和别人发过来的网络图片,这个框架会自动判断。
4.支持图片复用,一次只加载三张图片,优化内存
5.支持长图、动态图、下载进度显示

二.效果图

当时项目需求如图所示,由于大部分第三方回去的时候会存在抖动问题,所以当时自己简单写了个浏览器,后来其他项目也用到了,所以做了扩展和优化,进行了图片复用,一次只加载三张图片,优化了内存,同时支持长图和动态图,以及进度下载进度显示。

1.UIScrollView 效果图:


UIScrollView_example.gif

2.UICollectionView 模仿微博模式 效果图:


UICollectionView_微博模式.gif

3.UICollectionView 模仿微信模式 效果图:


UICollectionView_微信模式.gif

二.实现方法

(1).创建FJPhotosView实例

FJPhotosView *photosView = [[FJPhotosView alloc] init];
// self.imageArray:              大图url数组 
// selectedIndex:                当前选中图片索引 
// photoViewShowType:            显示模式 
[photosView setParam:self.bigImageArray selectedIndex:indexPath.row photoViewShowType:self.switchShowBtn.selected];
// 设置代理 
photosView.delegate = self;
// 展示图片浏览器
 [photosView show];

其中:

// 显示 模式
typedef NS_ENUM(NSInteger, PhotoViewShowType){
    // 模仿微博显示
    PhotoViewShowTypeOfWeiBo = 0,
    // 模仿微信显示
    PhotoViewShowTypeOfWeiXin = 1,
};

如果

(2).实现代理方法 -- FJPhotosViewDelegate

// 返回临时占位图片(即原来的小图)

- (UIImageView *)photoBrowser:(FJPhotosView *)browser placeholderImageForIndex:(NSInteger)index { 
     //获取占位小图代码;
} 

// 返回临时占位图片位置

-(CGRect)photoBrowser:(FJPhotosView *)browser targetRectForIndex:(NSInteger)index {
     //获取占位图片位置代码;
}

三.代码解析

1.架构解析

A.FJPhotosView:

a.FJPhotosView图片浏览器入口,加载在[UIApplication sharedApplication].keyWindow上,这样可以有效的避免消失时的抖动情况。
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {

        self.backgroundColor = [UIColor blackColor];
    
        self.frame = [UIApplication sharedApplication].keyWindow.bounds;
    
        [[UIApplication sharedApplication].keyWindow addSubview:self];
    }
    return self;
}

b.FJPhotosView拥有一个最外围UIScrollView负责容纳显示视图和一个UILabel负责显示当前序号,然后根据传入的参数进行初始化

B.FJPhotoScrollView:

a.FJPhotoScrollView继承自FJBaseScrollView,本身是一个UIScrollView,拥有myImageView(主展示图)、originalImageView(占位小图)和progressLayer(进度条):

// 展示图
@property(nonatomic, strong)  UIImageView *myImageView;
// 进度条
@property (nonatomic, strong) CAShapeLayer *progressLayer;
// 占位图
@property (nonatomic, strong) UIImageView *originalImageView;

b.FJPhotoScrollView通过设置参数函数进行图片类型判断、动画效果选择、下载进度显示。
核心函数:
// 设置相关参数
- (void)setParamWithVariable:(id)variable currentIndex:(NSInteger)currentIndex photoViewShowType:(PhotoViewShowType)photoViewShowType {

    //初始位置
    _variable = variable;
    self.currentIndex = currentIndex;
    // 获取原图位置
    CGRect originalRect = [self targetRectForIndex:currentIndex];
    self.myImageView.frame = originalRect;
    // 获取 临时占位图
    self.originalImageView = [self placeholderImageForIndex:currentIndex];

    // 微博 显示 方式 (直接放大)
    if (photoViewShowType == PhotoViewShowTypeOfWeiBo) {
    
        [self showDirectlyAmplifyPhotoViewAnimation:_variable];
    }

    // 微信 显示 方式 (加载完成后 放大)
    else if(photoViewShowType == PhotoViewShowTypeOfWeiXin && [self isImageUrl:variable]) {
        // 图片未下载 先显示在 中部
        NSString *tmpImageUrl = (NSString *)variable;
        if ([self.myImageView exitCurrentImage:tmpImageUrl] == NO) {
            [UIView animateWithDuration:FJDefaultAnimationTime animations:^{
            
                [self setMyimageViewInTheMiddle:self.originalImageView];
            
            } completion:^(BOOL finished) {
            
                [self showImageViewDowningProgerss:tmpImageUrl isAnimation:YES];
            }];
        }
        // 图片已下载 直接放大
        else {
            [self showDirectlyAmplifyPhotoViewAnimation:_variable];
        }
    }
}

variable 之所以是id类型,主要是为了做本地图片和网络图片的兼容,通过函数isImageUrl判断是否为网络图片:

// 判断 是否 为 网络 图片
- (BOOL)isImageUrl:(id)variable {
    BOOL isImageUrl = NO;
    // NSString 类型
    if ([variable isKindOfClass:[NSString class]]) {
        NSString *tmpStr = (NSString *)variable;
        isImageUrl = [tmpStr isHttpUrl];
    }
    return isImageUrl;
}

如果非网络图片就是本地图片,本地图片就直接动画放大显示,如果是网络图片,判断是否下载,如果未下载,判断如果是微信模式,就先居中显示,然后去下载,如果是微博模式,就直接放大下载。

// 显示 直接 放大 图片 动画
- (void)showDirectlyAmplifyPhotoViewAnimation:(id)variable {

    [UIView animateWithDuration:FJDefaultAnimationTime animations:^{
    
        [self setFrameAndZoom:self.originalImageView];
    
    } completion:^(BOOL finished) {
    
        self.userInteractionEnabled = YES ;
        // 网络 图片
        if ([self isImageUrl:variable]) {
        
            [self showImageViewDowningProgerss:(NSString *)_variable isAnimation:NO];
        
        }
        // 本地 图片
        else {
            //变换完动画 从网络开始加载图
            self.myImageView.image = [self getImage:variable];
            [self setFrameAndZoom:self.myImageView];//设置最新的网络下载后的图的frame大小
        }
    }];
}

网络图片显示下载进度,下载完成后动画展现

// 显示 下载进入和 下载完成 展现 动画
- (void)showImageViewDowningProgerss:(NSString *)imageUrl isAnimation:(BOOL)isAnimation{
    //变换完动画 从网络开始加载图
    NSString *imageUrlStr = [[imageUrl stringByReplacingOccurrencesOfString:@"\\" withString:@""] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [self.myImageView sd_setImageWithURL:[NSURL URLWithString:imageUrlStr] placeholderImage:self.myImageView.image       options:SDWebImageRetryFailed|SDWebImageLowPriority progress:^(NSInteger receivedSize, NSInteger expectedSize) {
    
        if (receivedSize > 0 && expectedSize > 0) {
            CGFloat progress = receivedSize / (float)expectedSize;
            progress = progress < 0.01 ? 0.01 : progress > 1 ? 1 : progress;
            if (isnan(progress)) progress = 0;
            self.progressLayer.hidden = NO;
            self.progressLayer.strokeEnd = progress;
        }
    
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
    
        self.progressLayer.hidden = YES;
        if (isAnimation) {
            [UIView animateWithDuration:FJDefaultAnimationTime animations:^{
                [self setFrameAndZoom:self.myImageView];
            }];
        }
        else {
             [self setFrameAndZoom:self.myImageView];
       }
    }];
}

微信模式未下载情况先显示在中间函数:

// 微信 模式 还没下载完成 显示在中间
- (void)setMyimageViewInTheMiddle:(UIImageView *)imageView {
    //设置空image时的情况
    //ImageView.image的大小
    CGFloat   imageH;
    CGFloat   imageW;

    if(imageView == nil) {
    
        imageH = self.myImageView.height;
        imageW = self.myImageView.width;
    
        self.myImageView.image = IMG(@"default_avatar_geren_134.png");
    
    }
    else {
        imageW  = imageView.width;
        imageH = imageView.height;
    
        if (imageW < 0.5 || imageH < 0.5) {
        
            imageH = self.myImageView.height;
            imageW = self.myImageView.width;
        }
        self.myImageView.image = imageView.image;
    }

    if (imageW < 0.5 || imageH < 0.5) {
        imageH = SCREEN_WIDTH / 2.5;
        imageW = SCREEN_WIDTH / 2.5;
    }
    CGFloat imageX = (SCREEN_WIDTH/2.0) - (imageW/2.0);
    CGFloat imageY = (SCREEN_HEIGHT/2.0) - (imageH/2.0);

    self.myImageView.frame = CGRectMake(imageX, imageY, imageW, imageH);
}

下载完成后或是本地图片直接放大以及图片缩放被说计算函数:

// 微博 模式 直接 放大
-(void)setFrameAndZoom:(UIImageView *)imageView {

    //ImageView.image的大小
    CGFloat   imageH;
    CGFloat   imageW;


    //设置空image时的情况
    if(imageView.image == nil || imageView.image.size.width == 0 || imageView.image.size.height == 0) {
        //设置主图片
        imageH = SCREEN_HEIGHT;
        imageW = SCREEN_WIDTH;
        self.myImageView.image = IMG(@"default_avatar_geren_134.png");
    
    }
    //不空
    else{
        //设置主图片
        imageW  = imageView.image.size.width;
       imageH = imageView.image.size.height;
        self.myImageView.image = imageView.image;
    }

    //设置主图片Frame 与缩小比例
    //横着
    if(imageW >= (imageH * (SCREEN_WIDTH/SCREEN_HEIGHT))){
    
        //设置居中frame
        CGFloat  myX_ =  0;
        CGFloat  myW_ = SCREEN_WIDTH;
        CGFloat  myH_  = myW_ *(imageH/imageW);;
        CGFloat  myY_ = SCREEN_HEIGHT - myH_ - ((SCREEN_HEIGHT - myH_)/2);
    
        self.myImageView.frame = CGRectMake(myX_, myY_, myW_, myH_);
        if (myH_ > SCREEN_HEIGHT) {
            self.contentSize = CGSizeMake(SCREEN_WIDTH, myH_);
        }

        //判断原图是小图还是大图来判断,是可以缩放,还是可以放大
        if (imageW >  myW_) {
            self.maximumZoomScale = 3*(imageW/myW_ ) ;//放大比例
        
          }
        else{
            self.minimumZoomScale = (imageW/myW_);//缩小比例
        
        }
    }
    //竖着
    else {
        CGFloat  myX_ = 0;
        CGFloat  myY_ = 0;
        CGFloat  myW_ = SCREEN_WIDTH;
        CGFloat  myH_ = floor(imageH / (imageW / self.width));
        if (myH_ > SCREEN_HEIGHT) {
            self.contentSize = CGSizeMake(SCREEN_WIDTH, myH_);
        }

        //变换设置frame
        self.myImageView.frame = CGRectMake(myX_, myY_, myW_, myH_);
    
        //判断原图是小图还是大图来判断,是可以缩放,还是可以放大
    
        if (imageH >  myH_) {
            self.maximumZoomScale =  3*(imageH/myH_ ) ;//放大比例
        
        }
        else {
            self.minimumZoomScale = (imageH/myH_);//缩小比例
        }
    }
}

实现单击,如果当前滚动到的图片在原主界面可视范围内,就动态返回原主界面当前滚动到的图片位置,如果不在当前可视范围内,就动画消失函数:
// tap事件
- (void)singleTap {
CGRect originalRect = [self targetRectForIndex:_currentIndex];
self.userInteractionEnabled = NO;
self.zoomScale = 1;

    [UIView animateWithDuration:0.5 animations:^{
        if (CGRectEqualToRect(originalRect, CGRectZero)) {
            self.alpha = 0;
        }else{
            self.myImageView.frame = originalRect;
        }
        self.superview.superview.backgroundColor = [UIColor clearColor];
        self.superview.backgroundColor = [UIColor clearColor];
        self.backgroundColor = [UIColor clearColor];
    } completion:^(BOOL finished) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewDidClick)]) {
            [self.delegate performSelector:@selector(scrollViewDidClick)];
        }
    }];
}

C.FJBaseScrollView

a.FJBaseScrollView是FJPhotoScrollView的基类,主要实现单击返回原主界面,双击将图片放大的效果,
通过touchesBegan函数来识别单击和双击:

// touch begin 标识双击和单击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    NSTimeInterval delaytime = 0.3;
    _point = [[touches anyObject] locationInView:self];
    switch (touch.tapCount) {
        case 1:
            break;
        case 2: {
            [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(singleTap) object:nil];
            [self performSelector:@selector(doubleTap) withObject:nil afterDelay:delaytime];
            break;
        default:
            break;
        }
    }
}


// touch end
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    switch (touch.tapCount) {
        case 1:
            [self performSelector:@selector(singleTap) withObject:nil afterDelay:.2];
            break;
        default:
            break;
    }
}

双击调用缩放方法:

 // 双击
-(void)doubleTap {
    if(_isScaled == YES){
        [self zoomToPointInRootView:_point atScale:1];
        _isScaled = NO;
    }else{
        [self zoomToPointInRootView:_point atScale:2];
        _isScaled = YES;
    }
}

设置UIScrollView缩放效果:

// 放大 或 缩小
- (void)zoomToPointInRootView:(CGPoint)center atScale:(float)scale {
    CGRect zoomRect;
    zoomRect.size.height = self.frame.size.height / scale;
    zoomRect.size.width  = self.frame.size.width  / scale;
    zoomRect.origin.x    = center.x - (zoomRect.size.width  / 2.0);
    zoomRect.origin.y    = center.y - (zoomRect.size.height / 2.0);
    [self zoomToRect:zoomRect animated:YES];
}

单击调用代理动态返回原来界面:

// 单击
-(void)singleTap {
    if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewDidClick)]) {
        [self.delegate performSelector:@selector(scrollViewDidClick)];
    }
}

四.最后

送给大家一张很喜欢的图:

加油.jpg

这是gitHub链接地址,大家有兴趣可以看一下,如果觉得不错,麻烦给个喜欢或star,如果有问题请及时反馈,谢谢!

上一篇 下一篇

猜你喜欢

热点阅读