博客> 模仿qq图片浏览器
模仿qq图片浏览器
2018-05-22 07:21 评论:0 阅读:616 艾江山2014
ios 图片浏览 QQ

简书原文 效果图:

qq相册效果.gif

    如何实现,首先想到的是用collectionView然后再添加手势,如果是使用scrollView图片多的时候复用就是问题。以直接选择collectionView然后设置flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

-(UICollectionView *)collectionView{
    if (!_collectionView) {
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
        flowLayout.scrollDirection             = UICollectionViewScrollDirectionHorizontal;
        flowLayout.itemSize                    = CGSizeMake((MainSize.width - 3*padding)/3, pictureHeight);
        _collectionView                        =\
        [[UICollectionView alloc]initWithFrame:self.fixedRect collectionViewLayout:flowLayout];
        _collectionView.backgroundColor        = [UIColor lightGrayColor];
        _collectionView.showsHorizontalScrollIndicator = NO;
        _collectionView.dataSource             = self;
        _collectionView.contentSize            = CGSizeMake(flowLayout.itemSize.width+padding * self.imageArrayM.count, pictureHeight);
        _collectionView.directionalLockEnabled = YES;
        [_collectionView registerClass:[AIPictureCollectionViewCell class] forCellWithReuseIdentifier:identifier];
    }
    return _collectionView;
}

现在的qq相片浏览器是在最下方,我这么暂时把frame写在最下方了,如果需要可以调整

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        //固定大小
        self.frame            = self.fixedRect;
        //collectionView
        [self addSubview:self.collectionView];
    }
    return self;
}

很快完成了collectionView但是接下来处处是坑。

  • 坐标转换问题,由于图片的父控件是在cell上的cell又是在collectionView上,就会出现这种效果: 不在window的效果.png 让后我就把图片加到window上这里需要注意的是,世界坐标和当前view的坐标转换

/**
 竖直滑动

 @param recognizer 手势
 */
-(void)verticalActionWithRecognizer:(UIPanGestureRecognizer*)recognizer{
    CGPoint cellCenterPoint   = CGPointZero;
    CGPoint worldCenterPoint  = CGPointZero;
    //手势移动了多远
    CGPoint translation       = [recognizer translationInView:self.contentView];
    UIWindow *lastWindow      = [[UIApplication sharedApplication].windows lastObject];
    //        AILog(@"竖直移动");
    cellCenterPoint   = CGPointMake(recognizer.view.center.x,
                                    translation.y + recognizer.view.center.y);
    //转回原来的坐标   不是第一次的时候
    if (self.isOnWindow) {
        cellCenterPoint         = [self.contentView convertPoint:cellCenterPoint fromView:lastWindow];
    }
    [lastWindow addSubview:recognizer.view];
    //转换为世界坐标
    worldCenterPoint            = [self.contentView convertPoint:cellCenterPoint toView:lastWindow];
    recognizer.view.center      = worldCenterPoint;
    self.onWindow               = YES;
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.contentView];
}
  • 手势选择问题,一开始我就选择 UIPanGestureRecognizer但是我发现使用 UIPanGestureRecognizer横向滑动事件也被 Cell拦截了。这时候你可能会想到用 UISwipeGestureRecognizer但是UISwipeGestureRecognizer不是连续手势,不能有拖拽效果。
    • 解决方案1: (不适用) 先在 cell上添加UISwipeGestureRecognizerUISwipeGestureRecognizer事件响应后再添加UIPanGestureRecognizer,这个方法可行但是第一次手势已经过去了才添加的UIPanGestureRecognizer手势并没有识别到当前手势,所以要上滑两次才能把图片滑出。
    • 解决方案2:(不适用) 只填加UIPanGestureRecognizer,判断出是横向滑动的时候通过代理方式把移动的距离传给collectionView然后通过contentOffset使外面的collectionView滑动,但是这个方法没有了scollview自带的边缘弹簧效果。所以弃用
    • 解决方案3: 使用UIGestureRecognizerDelegate在手势代理方法里有个方法 大概意思是:是否支持多手势触发,返回YES,则可以多个手势一起触发方法,返回NO则为互斥 是否允许多个手势识别器共同识别,一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO;如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行,否则上层对象识别后则不再继续传播
// note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

现在只需要在这个方法里返回YES就可以直接把手势的事件传递到下一个手势,也就是让scollview接收手势

  • UIPanGestureRecognizerUIScrollViewPanGestureRecognizer一起接收了手势,在左右滑动的时候ScrollView也会跟着滑动

未锁定scrollview.gif

这个时候我通过判断imageView是否在window上,然后用代理方式通知collectionView设置scrollEnabled属性

-(void)pictureCollection:(AIPictureCollectionViewCell *)pictureCollectionCell lockScollViewWithOnWindow:(BOOL)isOnWindow{
    self.collectionView.scrollEnabled = !isOnWindow;
}

    好了坑填得差不多了,接下来是当松手的时候判断是发送出去还是返回,这部分比较简单我就直接贴代码了 松手时:

  if (recognizer.state == UIGestureRecognizerStateEnded ) {//松手的时候执行
        if (self.direction == kPictureMoveDirectionUp ||
            self.direction == kPictureMoveDirectionDown) {
            //返回最后的本地viewCenter的坐标
            CGPoint endPoint =  [self.contentView convertPoint:recognizer.view.center fromView:lastWindow];
            //判断距离
            if (endPoint.y < 0 && self.isOnWindow) {//发送出去

                [self sendImageRecognizer:recognizer];

            }else {//返回cell上
                [self backImageRecognizer:recognizer];
            }
        }
        self.onWindow = NO;
        //解锁scolliew
        if (self.delegate && [self.delegate respondsToSelector:@selector(pictureCollection:lockScollViewWithOnWindow:)]) {

            [self.delegate pictureCollection:self lockScollViewWithOnWindow:self.isOnWindow];
        }
    }

发送

/**
 发送图片

 @param recognizer 手势
 */
-(void)sendImageRecognizer:(UIPanGestureRecognizer*)recognizer{
    AILog(@"发出去");
    UIImageView *imageV = (UIImageView*)recognizer.view;
    __weak typeof(self) weakSelf = self;
    if (self.delegate && [self.delegate respondsToSelector:@selector(pictureCollection:didGestureSelectedImage:andImageWorldRect:)]) {
        [self.delegate pictureCollection:self didGestureSelectedImage:imageV.image andImageWorldRect:recognizer.view.frame];
    }
    //设置动画初始frame
    imageV.frame  = CGRectMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5, 0, 0);
    [self.contentView addSubview:imageV];
    [UIView animateWithDuration:.3 animations:^{
        imageV.frame = weakSelf.bounds;
    } completion:^(BOOL finished) {

    }];
}

发送Controller中代码

#pragma mark -AIPictureViewerDelegate
-(void)pictureViewer:(AIPictureViewer *)pictureViewer didGestureSelectedImage:(UIImage *)image andImageWorldRect:(CGRect)imageWorldRect{
    UIImageView *imageView                = [[UIImageView alloc]initWithImage:image];
    imageView.frame                       = imageWorldRect;
    [self.view addSubview:imageView];
    POPBasicAnimation *popAnimation       =   [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition];
    popAnimation.toValue                  =   [NSValue valueWithCGPoint:self.imageV.center];
    popAnimation.duration                 =   0.5;
    popAnimation.timingFunction           =   [CAMediaTimingFunction functionWithName:kCAAnimationLinear];
    [imageView.layer pop_addAnimation:popAnimation forKey:nil];
    //动画完成后赋值
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(popAnimation.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [imageView removeFromSuperview];
        self.imageV.image = image;
    });
}

返回

/**
 返回图片

 @param recognizer 手势
 */
-(void)backImageRecognizer:(UIPanGestureRecognizer*)recognizer{
    AILog(@"返回");
    __weak typeof(self) weakSelf = self;
    UIWindow *lastWindow      = [[UIApplication sharedApplication].windows lastObject];
    //添加动画
    CGRect worldOrginalRect              = [self.contentView convertRect:self.bounds toView:lastWindow];
    //            一开始要记下frame,动画在window上做,完成后再加到contentView上
    [UIView animateWithDuration:.5 animations:^{
        recognizer.view.frame = worldOrginalRect;
    } completion:^(BOOL finished) {
        [weakSelf.contentView addSubview:recognizer.view];
        recognizer.view.frame =  self.bounds;

    }];
}

    demo中有使用reactiveCocoa但是完全可以替换不使用,cell传递选中Image的时候是使用两次代理,实际项目中使用通知方式更为方便,这里为了方便他人使用而以代理形式传值。 完整项目请点github喜欢的点个start支持下

收藏
0
sina weixin mail 回到顶部