利用UICollectionView实现横向居中放大滚动的循环浏览

实现这个功能主要分步实现两个内容,首先视图横向滚动放大,然后实现左右的循环滚动。

一、横向居中放大滚动

在自定义UICollectionViewFlowLayout设置滚动动画,动画的使用参考文章中的第一篇参考文章

首先自定义一个UICollectionViewFlowLayout

//设置放大动画  
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect  
{  
    NSArray *arr = [self getCopyOfAttributes:[super layoutAttributesForElementsInRect:rect]];  
    //屏幕中线  
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width/2.0f;  
    //刷新cell缩放  
    for (UICollectionViewLayoutAttributes *attributes in arr) {  
        CGFloat distance = fabs(attributes.center.x - centerX);  
        //移动的距离和屏幕宽度的的比例  
        CGFloat apartScale = distance/self.collectionView.bounds.size.width;  
        //把卡片移动范围固定到 -π/4到 +π/4这一个范围内  
        CGFloat scale = fabs(cos(apartScale * M_PI/4));  
        //设置cell的缩放 按照余弦函数曲线 越居中越趋近于1  
        attributes.transform = CGAffineTransformMakeScale(1.0, scale);  
    }  
    return arr;  
}  
//防止报错 先复制attributes
- (NSArray *)getCopyOfAttributes:(NSArray *)attributes
{
    NSMutableArray *copyArr = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        [copyArr addObject:[attribute copy]];
    }
    return copyArr;
}

//是否需要重新计算布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return true;
}

通过该CollectionViewFlowLayout创建collectionView

-(void)createUI{
    JLXUICollectionViewFlowLayout *layout = [[JLXUICollectionViewFlowLayout alloc] init];
    layout.itemSize = CGSizeMake(474*kJLXWidthScale, 848*kJLXHeightScale);
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    layout.minimumLineSpacing = 50*kJLXWidthScale;
    layout.minimumInteritemSpacing = 50*kJLXWidthScale;
    self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
    [self.collectionView setBackgroundColor:kJLXBackgroundColor];
    self.collectionView.delegate = self;
    self.collectionView.dataSource = self;
    self.collectionView.pagingEnabled = NO;
    self.collectionView.showsHorizontalScrollIndicator = NO;
    [self.view addSubview:self.collectionView];
    [self.collectionView registerClass:[HomeCaseCell class] forCellWithReuseIdentifier:reuseIdentifier];
    [self.view addSubview:self.collectionView];

    [self.collectionView setFrame:CGRectMake(0, 40*kJLXHeightScale, JLXScreenSize.width, JLXScreenSize.height)];
    self.collectionView.contentSize = CGSizeMake(self.caseArray.count*JLXScreenSize.width, 0);
}

因为要调整cell的边距缩进,所以我们在代理方法中修改了collection的EdgeInsets

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    //自定义item的UIEdgeInsets
    return UIEdgeInsetsMake(0, self.view.bounds.size.width/2.0-474*kJLXWidthScale/2, 0, self.view.bounds.size.width/2.0-474*kJLXWidthScale/2);
}

然后在拖动开始和结束时记录滚动的距离,调整居中

//手指拖动开始
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.m_dragStartX = scrollView.contentOffset.x;
}

//手指拖动停止
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    self.m_dragEndX = scrollView.contentOffset.x;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self fixCellToCenter];
    });
}

//配置cell居中
- (void)fixCellToCenter {
    //最小滚动距离
    float dragMiniDistance = self.view.bounds.size.width/20.0f;
    if (self.m_dragStartX -  self.m_dragEndX >= dragMiniDistance) {
        self.m_currentIndex -= 1;//向右
    }else if(self.m_dragEndX -  self.m_dragStartX >= dragMiniDistance){
        self.m_currentIndex += 1;//向左
    }
    NSInteger maxIndex = [_collectionView numberOfItemsInSection:0] - 1;
    
    
    self.m_currentIndex = self.m_currentIndex <= 0 ? 0 : self.m_currentIndex;
    self.m_currentIndex = self.m_currentIndex >= maxIndex ? maxIndex : self.m_currentIndex;
    
    
    [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.m_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}

二、实现循环滚动

看了几个循环滚动的文章,比如第三篇参考文章,发现并不适合现在的情况,因为现存的几乎全是cell铺满整个屏幕,而现在的这个是半屏放大的,所以不能采用

在-(void)scrollViewDidEndDecelerating:(UIScrollview *)scrollview函数中通过scrollView 的偏移量来判断是否到达两个边界,再分别进行处理这个方案。

所以需要使用其他方案知道当前的cell的indexpath,通过参考第四个参考文章获取到了indexpath,但是在

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    // 将collectionView在控制器view的中心点转化成collectionView上的坐标
    CGPoint pInView = [self.view convertPoint:self.collectionView.center toView:self.collectionView];
    // 获取这一点的indexPath
    NSIndexPath *indexPathNow = [self.collectionView indexPathForItemAtPoint:pInView];
    // 赋值给记录当前坐标的变量
    self.currentIndexPath = indexPathNow;
    // 更新底部的数据 
    // ...
}

获取的时候并不准确,比如我没有采用动画,而是手指一直拖动cell的话,这个函数就不会调用,所以索性就使用了m_currentIndex来记录indexpath。

因为是左右滚动,并且不是全屏的,所以在初始化的时候数组的对象循环放四次,这时候数组中存放的对象如下图所示,初始位置放在下图中B的位置

-(void)loadData{
    NSArray *array = [NSArray arrayWithObjects:@"img1",@"img2",@"img3",@"img4", nil];
    self.caseArray = [NSMutableArray array];
    ///加四次为了循环
    for (int i=0; i<4; i++) {
        [self.caseArray addObjectsFromArray:array];
    }
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];
    [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:self.caseArray.count/2 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    self.m_currentIndex = self.caseArray.count/2;
}

20170534.png

这样存放的意义就是左右都是有图片的,当往右滚动到C的位置的时候,将collectionview的位置恢复到B,当往左滚动到A的位置的时候,也将collectionview的位置恢复到B,这样就不会出现左右边界空白的情况了。

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    if (self.m_currentIndex == [self.caseArray count]/4*3) {
        NSIndexPath *path  = [NSIndexPath indexPathForItem:[self.caseArray count]/2 inSection:0];
        [self.collectionView scrollToItemAtIndexPath:path atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
        self.m_currentIndex = [self.caseArray count]/2;
    }
    else if(self.m_currentIndex == [self.caseArray count]/4){
        NSIndexPath *path = [NSIndexPath indexPathForItem:[self.caseArray count]/2 inSection:0];
        [self.collectionView scrollToItemAtIndexPath:path atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
        self.m_currentIndex = [self.caseArray count]/2;
    }
}

效果动图演示

Demo下载

Github下载:https://github.com/DamonHu/CollectionCircleDemo

Gitosc下载:http://git.oschina.net/DamonHoo/CollectionCircleDemo

参考文章

Last modification:May 24th, 2017 at 02:01 pm
如果看了这个文章可以让你少加会班,可以请我喝杯可乐
已打赏名单
微信公众号

Leave a Comment