iOS系统相册滑动效果

非常渴望系统相册滚动的效果.经过一天的研究.终于达成所愿

效果1:中间会有间距,并且每次滚动刚好是一个屏宽+间距宽

WechatIMG6.jpeg

效果2:左右两张图均有少许偏移.效果极好.
偏移效果2018-09-09 09.15.12.gif

首先:实现效果1.通过四套方案层层分析

方案一:将Cell的宽度设定为屏宽+间距!随后再添加一个类似contentView.设定其宽度为屏宽.背景色设定为黑色即可!因pagingEnabled每次滚动一个屏幕宽.而不是collection的ItemSize的宽度!所以可达效果
    -(UICollectionView *)collectionView
    {
        if (!_collectionView) {

            UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout alloc]init];
            flowLayout.itemSize = CGSizeMake(self.view.frame.size.width +10, self.view.frame.size.height);
            flowLayout.minimumLineSpacing = 0;
            flowLayout.minimumInteritemSpacing = 0;
            flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
            _collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(-10, 0, self.view.frame.size.width+10, self.view.frame.size.height) collectionViewLayout:flowLayout];
            [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCellID"];
            _collectionView.delegate = self;
            _collectionView.dataSource = self;
            _collectionView.pagingEnabled = YES;
            _collectionView.showsVerticalScrollIndicator = NO;
            _collectionView.showsHorizontalScrollIndicator = NO;

        }
        return _collectionView;
    }

    -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCellID" forIndexPath:indexPath];
        NSLog(@"------------------------->当前索引数:%zd",indexPath.row);

        if ([cell viewWithTag:1000]) {
            UILabel *label = [cell viewWithTag:1000];
            label.text = [NSString stringWithFormat:@"%zd",indexPath.row];
        }else{
            UILabel *label = [[UILabel alloc]init];
            label.frame = CGRectMake(10, 0, self.view.frame.size.width, self.view.frame.size.height);
            label.tag = 1000;
            label.text = [NSString stringWithFormat:@"%zd",indexPath.row];
            label.backgroundColor = [UIColor colorWithRed:((float)arc4random_uniform(256) / 255.0) green:((float)arc4random_uniform(256) / 255.0) blue:((float)arc4random_uniform(256) / 255.0) alpha:1.0];
            [cell.contentView addSubview:label];
        }

        cell.backgroundColor = [UIColor blackColor];

        return cell;
    }
方案二:scrollViewWillEndDragging方式实现.确定停止时的位置.每次滚动一页.也能实现.但是效果不是很理想.停止的有点缓慢
    - (CGPoint)nearestTargetOffsetForOffset:(CGPoint)offset
    {
        CGFloat pageSize = BUBBLE_DIAMETER + BUBBLE_PADDING;
        NSInteger page = roundf(offset.x / pageSize);
        CGFloat targetX = pageSize * page;
        return CGPointMake(targetX, offset.y);
    }

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
    {
        if (velocity.x > 0) {
            self.selectIndex += 1;
            if (self.selectIndex > [self.collectionView numberOfItemsInSection:0]) {
                self.selectIndex = [self.collectionView numberOfItemsInSection:0];
            }
        }else if(velocity.x < 0){
            self.selectIndex -= 1;
            if (self.selectIndex < 0) {
                self.selectIndex = 0;
            }
        }
        CGPoint offset = *targetContentOffset;
        CGFloat pageSize = BUBBLE_DIAMETER + BUBBLE_PADDING;
        CGFloat targetX = pageSize * self.selectIndex;


        CGPoint targetOffset = CGPointMake(targetX, offset.y);
        targetContentOffset->x = targetOffset.x;
        targetContentOffset->y = targetOffset.y;

    }

    -(UICollectionView *)collectionView
    {
        if (!_collectionView) {

            UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout alloc]init];
            flowLayout.itemSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
            flowLayout.minimumLineSpacing = 10;
            flowLayout.minimumInteritemSpacing = 0;
            flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
            _collectionView = [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
            [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCellID"];
            _collectionView.delegate = self;
            _collectionView.dataSource = self;
            _collectionView.showsVerticalScrollIndicator = NO;
            _collectionView.showsHorizontalScrollIndicator = NO;

        }
        return _collectionView;
    }

    -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        FQ_CollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"FQ_CollectionViewCellID" forIndexPath:indexPath];
        if (indexPath.row % 2) {
            cell.backgroundColor = [UIColor redColor];
        }else{
            cell.backgroundColor = [UIColor orangeColor];
        }
        return cell;
    }
方案三.设置collection的宽度为屏宽+间距.itemSize的宽度为屏宽.设定minimumLineSpacing为间距值.也能达到效果
    -(UICollectionView *)collectionView
    {
        if (!_collectionView) {

            UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout alloc]init];
            flowLayout.itemSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
            flowLayout.minimumLineSpacing = BUBBLE_PADDING;
            flowLayout.minimumInteritemSpacing = 0;
            flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

            _collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, BUBBLE_DIAMETER + BUBBLE_PADDING, self.view.bounds.size.height) collectionViewLayout:flowLayout];
            [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCellID"];
            _collectionView.delegate = self;
            _collectionView.dataSource = self;
            _collectionView.pagingEnabled = YES;
            _collectionView.showsVerticalScrollIndicator = NO;
            _collectionView.showsHorizontalScrollIndicator = NO;
        }
        return _collectionView;
    }

    -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCellID" forIndexPath:indexPath];
        if (indexPath.row % 2) {
            cell.backgroundColor = [UIColor redColor];
        }else{
            cell.backgroundColor = [UIColor orangeColor];
        }
        return cell;
    }
方案四:最佳解决方案为自定义FQ_CollectionViewFlowLayout.继承自UICollectionViewFlowLayout.并且重写其相关代码.关键在于设置每一个UICollectionViewLayoutAttributes的frame.
    #import "FQ_CollectionViewCell.h"

    @implementation FQ_CustomCollectionViewLayoutAttributes

    -(id)copyWithZone:(NSZone *)zone
    {
        FQ_CustomCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
        attributes.progress = self.progress;
        return attributes;
    }

    @end


    @interface FQ_CollectionViewFlowLayout()

    @property (nonatomic, strong) NSMutableArray *attributesArray;

    @end

    @implementation FQ_CollectionViewFlowLayout

    +(Class)layoutAttributesClass
    {
        return [FQ_CustomCollectionViewLayoutAttributes class];
    }

    -(void)prepareLayout
    {
        [super prepareLayout];
        
        [_attributesArray removeAllObjects];
        
        NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
        //横向间距
        //self.minimumLineSpacing
        CGFloat indexX = 0.0f;
        
        for (int i = 0; i < cellCount ; ++i) {
            FQ_CustomCollectionViewLayoutAttributes * layoutAttributes = [FQ_CustomCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
            
            layoutAttributes.frame = CGRectMake(indexX, layoutAttributes.frame.origin.y, ScreenW, self.collectionView.bounds.size.height);
            
            if (i == [self getSelectCurrentIndex]) {
                layoutAttributes.progress = [self getScrollProgress];
            }else if(i == ([self getSelectCurrentIndex] + 1) && [self getSelectCurrentIndex] != cellCount - 1){
                layoutAttributes.progress = -(1 - [self getScrollProgress]);
            }else{
                layoutAttributes.progress = 0.0;
            }
            
            [self.attributesArray addObject:layoutAttributes];
            
            indexX = indexX + (ScreenW + self.minimumLineSpacing);
        }
        
        //这次根据情况添加
        [self.collectionView reloadData];
    }

    //2.提供布局属性对象
    -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        return self.attributesArray.copy;
    }


    //1.提供滚动范围
    -(CGSize)collectionViewContentSize
    {
        return CGSizeMake((ScreenW + self.minimumLineSpacing) * self.attributesArray.count - self.minimumLineSpacing, self.collectionView.bounds.size.height);
    }

    -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    {
        return YES;
    }

    -(int)getSelectCurrentIndex
    {
        int selectIndex = self.collectionView.contentOffset.x / (ScreenW + self.minimumLineSpacing);
        return selectIndex;
    }

    -(CGFloat)getScrollProgress{
        CGFloat progress = self.collectionView.contentOffset.x / (ScreenW + self.minimumLineSpacing) - [self getSelectCurrentIndex];
        return MAX(MIN(progress, 1), 0) ;
    }

    -(NSMutableArray *)attributesArray
    {
        if (!_attributesArray) {
            _attributesArray = [NSMutableArray array];
        }
        return _attributesArray;
    }

    @end

以上均能实现产生黑色间距效果.因为方案二未设置pagingEnabled属性.所以滚动停止效果不太理想!方案三与四最佳!如果仅仅是实现黑色间距的效果推荐方案三.如果还有其他效果需要操作推荐方案四!

随后:实现效果2.通过2套方案尝试实现:

失败思路:基于方案三

要想实现系统选中cell与即将显示cell之间的效果.最开始的思路在于:通过记录选中的cell.以及方法willDisplayCell获取即将出现的cell.即可确定左右滑动方向

1.0如果displayRow > self.selectIndex.即向➡️滑动.如果displayRow < self.selectIndex.即向⬅️滑动

2.0如果向➡️滑动.那么selectCell为左.displayCell为右.

2.1如果向⬅️滑动.那么displayCell为左.selectCell为右.

3.0根据动画效果分析.如果selectCell为左侧全部显示.displayCell图片只显示(屏宽-间距,即向右偏移-间距值).随着拖拽.selectCell会逐渐向左侧滑.而displayCell也是向左偏移.

3.1根据动画效果分析.如果selectCell为右侧全部显示.displayCell图片只显示(屏宽-间距,即向左偏移-间距值).随着拖拽.selectCell会逐渐向右侧滑.而displayCell也是向右偏移.

通过在scrollViewDidScroll滚动中的偏移值去计算其对应cell的偏移值:相关代码如下.(代码中的10为内容偏移最大值)

    //整个collection的偏移offsizeX
    CGFloat offsizeX = scrollView.contentOffset.x - (self.selectIndex == 0 ? 0 : (self.selectIndex - 1) * (self.view.width + 10)) - self.view.width;
    //实际cell内部控件的偏移值为offX
    CGFloat offX = (offsizeX * 10.0 + 10) / (self.view.width + 10);
    //即将显示的cell
    FQImagePreviewCell * displayCell = (FQImagePreviewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:self.willDisplayIndex inSection:0]];
    //当前选中的cell
    FQImagePreviewCell * selectCell = (FQImagePreviewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:self.selectIndex inSection:0]];

    if (self.willDisplayIndex > self.selectIndex) {
        
        [displayCell setDirectionLeft:NO isSelect:NO contentOffSize: (10 - offX)]; //减少
        [selectCell setDirectionLeft:YES isSelect:YES contentOffSize: offX];//增加
    }else if(self.willDisplayIndex < self.selectIndex){
        [displayCell setDirectionLeft:YES isSelect:NO contentOffSize: (10 + offX)];
        [selectCell setDirectionLeft:NO isSelect:YES contentOffSize: -offX];
    }

    //随后在cell中提供相关滚动的方法即可
    -(void)setDirectionLeft:(BOOL)isleft isSelect:(BOOL)isSelectIndex contentOffSize:(CGFloat)offsize
    {
        if (isSelectIndex && isleft) {       
            //选中index.并且在左边
            self.imageView.transform = CGAffineTransformMakeTranslation(offsize, 0.0);
        }else if (isSelectIndex && !isleft){
            //选中index.并且在右边边
            self.imageView.transform = CGAffineTransformMakeTranslation(-offsize, 0.0);
        }else if (!isSelectIndex && isleft){
            //即将显示showindex.并且在左边
            self.imageView.transform = CGAffineTransformMakeTranslation(offsize, 0.0);
        }else{
            //即将显示showindex.并且在右边
            self.imageView.left = -offsize;
        }
    }

也会有点效果.但是存在bug.但是大致思路应该是对的.

成功思路:基于方案四

核心思路

1.只有当前index以及下一个index

2.分析所得:当前index对应的偏移值为progress.下一个index偏移值为1-progress

3.随后在相关cell中重写-(UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes方法做效果即可

相关代码:

-(void)prepareLayout

    {
        
        [super prepareLayout];
        
        [_attributesArray removeAllObjects];
        
        NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
        
        CGFloat indexX = 0.0f;
        
        for (int i = 0; i < cellCount ; ++i) {
            
            FQ_CustomCollectionViewLayoutAttributes * layoutAttributes = [FQ_CustomCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
            
            layoutAttributes.frame = CGRectMake(indexX, layoutAttributes.frame.origin.y, ScreenW, self.collectionView.bounds.size.height);
            
            if (i == [self getSelectCurrentIndex]) {
                
                layoutAttributes.progress = [self getScrollProgress];
                
            }else if(i == ([self getSelectCurrentIndex] + 1) && [self getSelectCurrentIndex] != cellCount - 1){
                
                layoutAttributes.progress = -(1 - [self getScrollProgress]);
                
            }else{
                
                layoutAttributes.progress = 0.5;
                
            }
            
            [self.attributesArray addObject:layoutAttributes];
            
            indexX = indexX + (ScreenW + self.minimumLineSpacing);
            
        }
        
        [self.collectionView reloadData];
        
    }

    -(int)getSelectCurrentIndex

    {
        
        int selectIndex = self.collectionView.contentOffset.x / (ScreenW + self.minimumLineSpacing);
        
        return selectIndex;
        
    }

    -(CGFloat)getScrollProgress{
        
        CGFloat progress = self.collectionView.contentOffset.x / (ScreenW + self.minimumLineSpacing) - [self getSelectCurrentIndex];
        
        return MAX(MIN(progress, 1), 0) ;
        
    }

在相关cell中的处理

    -(UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(FQ_CustomCollectionViewLayoutAttributes *)layoutAttributes

    {
        
        [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
        
        self.textlabel.transform = CGAffineTransformMakeTranslation(layoutAttributes.progress * BUBBLE_PADDING, 0);
        
        return layoutAttributes;
        
    }

需要注意的是.因为自定义FQ_CustomCollectionViewLayoutAttributes为UICollectionViewLayoutAttributes的子类.除了在自定义的FQ_CollectionViewFlowLayout中重写

+(Class)layoutAttributesClass

{
    
    return [FQ_CustomCollectionViewLayoutAttributes class];
    
}

还需要在FQ_CustomCollectionViewLayoutAttributes重写

-(id)copyWithZone:(NSZone *)zone

{
    
    FQ_CustomCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
    
    attributes.progress = self.progress;
    
    return attributes;
    
}

保证自定义的FQ_CustomCollectionViewLayoutAttributes属性能被copy

最终效果:

ScreenRecording_09-30-2018 09-49-38.gif

相关demo的github链接
相关使用库的github链接

你可能感兴趣的:(iOS系统相册滑动效果)