使用UICollectionView+CATransform3D

作者: weiweilong | 来源:发表于2017-06-28 10:28 被阅读446次

    首先来看一下效果图:

    效果图.gif

    看完效果图觉得还不错那就继续往下看哦👇

    <h4>实现思路</h4>

    抛开滑动的3D效果,我们先来实现普通的分页滑动效果。

    要做这种卡片式的分页滑动,最先想到的应该就是用<code>UICollectionView</code>来实现了。但是<code>UICollectionView</code>的分页只能相对于本身的frame来做分页,要根据cell 的大小来做分页就需要来自定义分页了。

    现在来解决第一个问题:<b>自定义分页。</b>
    自定义分类想到两种方式:

    • 1 、一种是开启<code>UICollectionView</code>的分页,把<code>UICollectionView</code>的大小设置成分页的一个页面大小,然后再把<code>clipsToBounds</code>设置成NO,这样就可以使用自带的分页效果,并且被遮挡的部分也能显示出来了,但是这样 在当前页的边上部分是不能接受事件的,所以还需要自己处理事件接收。
    • 2 、实现UIScrollView的协议方法,监听滑动结束,计算出滑动到的当前页位置,然后自定义结束位置,将得到的当前页面居中。

    第二种方法只需要在滑动结束后,计算需要居中显示的页面,使用起来相对更加灵活。所以使用第二种来实现✨

    首先新建一个<code>UICollectionView</code>的子类,定义好需要使用的属性。

    @interface DCollectionView : UICollectionView
    @property (nonatomic, retain) NSArray *imgDatas;
    @property (nonatomic, assign) NSInteger currentIndex;
    @end
    

    .m中实现协议方法

    - (instancetype)initWithFrame:(CGRect)frame {
        _layout = [[LayoutView alloc]init];
        _layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        
        self = [super initWithFrame:frame collectionViewLayout:_layout];
        if(self){
            [self _initView];
        }
        return self;
    }
    - (void)_initView {
        _curIndex = 1;
        self.delegate = self;
        self.dataSource = self;
        self.pagingEnabled = NO;
     
        [self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"];
    }
    
    #pragma mark collection协议
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        return _imgDatas.count;
    }
    
    - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
        
        cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:_imgDatas[indexPath.row]]];
        cell.layer.masksToBounds = YES;
        cell.layer.cornerRadius = 8;
        return cell;
    }
    
    // 设置每个块的宽、高
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
        return CGSizeMake(itemWidth, itemHight);
    }
    
    // 四个边缘的间隙
    - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
        return UIEdgeInsetsMake(0, 5, 0, 5);
    }
    

    <code>UICollectionView</code>继承自<code>UIScrollView</code>,在<code>UIScrollViewDelegate</code>的协议方法<code>scrollViewDidEndDragging</code>中,根据滑动减速停止时的位置计算当前需要居中的页面。

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        NSInteger scrIndex = scrollView.contentOffset.x/(itemWidth + 10);
        
        [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:scrIndex + 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
        
        _curIndex = scrIndex + 1;
    }
    

    这样在滑动完成后继续滑动到计算出的cell页面,但这样只实现了在当手离开屏幕时,页面还在滑动时的效果,还需要在<code>scrollViewDidEndDragging</code>中计算出居中的页面。

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        NSInteger scrIndex = scrollView.contentOffset.x/(itemWidth + 10);
        // 当停止拖拽时,view不在滑动时才执行,还在滑动则调用scrollViewDidEndDecelerating
        if(!decelerate){
            [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:scrIndex + 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
            _curIndex = scrIndex + 1;
        }
    }
    

    最后再给点击cell,将点击的cell滑动居中即可:

    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
        [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
        _curIndex = indexPath.row;
    }
    

    自定义分页实现完成,下面在滑动中使用<code>CATransform3D</code>加上3D效果

    在上面<code>UICollectionView</code>的初始化方法中可以看到,<code>collectionViewLayout</code>是使用的自定义<code>UICollectionViewFlowLayout</code>。
    下面就在子类化<code>UICollectionViewFlowLayout</code>中来实现3D滑动的效果。

    主要在<code>layoutAttributesForElementsInRect</code>协议方法中,给视图元素利用<code>CATransform3DMakeScale</code>加上效果。那这个<code> layoutAttributesForElementsInRect</code>协议方法是做什么的呢?

    - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
    

    这个方法返回一个数组,是返回存放在CGRect范围内所有视图的布局属性的一个数组。下面实现方法:

    // 返回存放在CGRect范围内所有视图的布局属性的一个数组
    - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
        
        NSArray *attArray = [super layoutAttributesForElementsInRect:rect];
        CGRect visRect;
        visRect.origin = self.collectionView.contentOffset;
        visRect.size = self.collectionView.bounds.size;
        
        for (UICollectionViewLayoutAttributes *layoutAttribute in attArray) {
            CGFloat distance = CGRectGetMidX(visRect) - layoutAttribute.center.x;
            CGFloat norDistance = fabs(distance/ActiveDistance);
            CGFloat zoom = 1- ScaleFactor * norDistance;
            layoutAttribute.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
            layoutAttribute.zIndex = 1;
        }
        
        return attArray;
    }
    

    完成上面那个方法后,会发现3D效果是有了,但是滑动时并没有实时刷新layout,还需要实现<code>shouldInvalidateLayoutForBoundsChange</code>这个协议:

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

    到此效果图上面的效果已经实现了。最后给上<a href="https://github.com/weiweilong/CA3DView">demo</a>

    相关文章

      网友评论

      • Mr_fei:感谢大佬,这正是我需要的,写的太好了。思路描述的很清晰,对着写一下子就把效果做出来了。再次感谢大佬无私奉献。
      • 靖_king:牛皮牛皮,刚好解决了我项目的难点

      本文标题:使用UICollectionView+CATransform3D

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