美文网首页collectionview
UICollectionViewFlowLayout实现放大缩小

UICollectionViewFlowLayout实现放大缩小

作者: 月咏蝴蝶 | 来源:发表于2016-06-24 18:05 被阅读2603次

    好久没写过文章了,趁现在得空写一下最近实现的UICollectionViewFlowLayout实现的左右滑动功能,先上效果图:

    左右滑动效果

    注:这里的分页方式是使用UIScrollView的setContentOffset方法来实现的,动画效果上感觉原生的分页好,但是我不懂如何用pageEnable或其他更好的方法实现由三个cell显示在屏幕的分页(刚好能使一个在屏幕正中央),如果有朋友有更好的实现方式或者有用过现成的框架,请记得告诉我一下,谢谢!

    1. 首先我在StoryBoard里面设置UICollectionView的约束,设置距离左边和右边都是0,高度随着我这边自定义的TopView和BottomView改变

    2. 设置UICollectionViewCell的Size

        func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
            let sizeWidth = WIDTH - 84
            let sizeHeight = collectionView.bounds.size.height
            return CGSizeMake(sizeWidth, sizeHeight)
        }
    

    这里我设置UICollectionViewCell的尺寸是距离左右分别是42,然后高度和UICollectionView一样高

    1. 自定义的UICollectionViewFlowLayout
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.itemSize = CGSizeMake(itemWidth, itemHeight)
            self.scrollDirection = .Horizontal
            self.minimumLineSpacing = 6
        }
    

    首先设置UICollectionView的滚动方向以及Cell之间的最小间距,在这里我设置为6是因为视觉感官上左右两边的Cell不至于相差太远,在这里我放两张对比吧,不缩小和缩小过的图:

    间距为6缩小之后的UICollectionViewCell
    间距为6没有变化的UICollectionViewCell
    继续实现UICollectionViewFlowLayout里面的方法
        // 布局
        override func prepareLayout() {
            // scrollRate
            collectionView?.decelerationRate = UIScrollViewDecelerationRateNormal
            collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: collectionView!.bounds.width / 2 - (WIDTH - 84) / 2, bottom: 0, right: collectionView!.bounds.width / 2 - (WIDTH - 84) / 2)
        }
    

    在这里设置了UICollectionView的contentInset,设置了第一个cell和最后一个cell距离左边和右边距离,刚好让第一个cell和最后一个cell位于最屏幕最中间

        // 边界改变就重新布局,默认是false
        override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
            return true
        }
    

    这个方法表示只要显示的边界发生改变就重新布局(默认是false)

        // 返回所有的UICollectionViewLayoutAttributes
        override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            let array = super.layoutAttributesForElementsInRect(rect)
            // 可见矩阵
            let visiableRect = CGRectMake(self.collectionView!.contentOffset.x, self.collectionView!.contentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)
            for attributes in array! {
                // 不在可见区域的attributes不变化
                if !CGRectIntersectsRect(visiableRect, attributes.frame) {continue}
                let frame = attributes.frame
                let distance = abs(collectionView!.contentOffset.x + collectionView!.contentInset.left - frame.origin.x)
                let scale = min(max(1 - distance/(collectionView!.bounds.width), 0.85), 1)
                attributes.transform = CGAffineTransformMakeScale(scale, scale)
            }
            return array
        }
    

    这个方法首先取到所有的UICollectionViewLayoutAttributes然后对其进行放大缩小操作。
    visiableRect是当前屏幕的Rect,结合CGRectIntersectsRect方法来判断哪些cell显示在屏幕,好让我们对其进行操作
    distance其实就是计算左边或者右边的距离,如果刚好在那个位置(就是cell在中心)的时候,cell就最大
    最后就是transform变大变小了
    如果这里有些不懂的话,可以去看看这篇文章:
    How to Create an iOS Book Open Animation: Part 1
    这里多说一个方法,如果想连续滑动几个Cell的话,不设置分页功能的话,可以再加这个方法,这个方法是滑动的时候始终让一个Cell保持在屏幕正中央

        // 当停止滑动,时刻有一张图片是位于屏幕最中央的。
        override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
            let lastRect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)
            //获得collectionVIew中央的X值(即显示在屏幕中央的X)
            let centerX = proposedContentOffset.x + self.collectionView!.frame.width * 0.5;
            //这个范围内所有的属性
            let array = self.layoutAttributesForElementsInRect(lastRect)
            //需要移动的距离
            var adjustOffsetX = CGFloat(MAXFLOAT);
            for attri in array! {
                if abs(attri.center.x - centerX) < abs(adjustOffsetX) {
                    adjustOffsetX = attri.center.x - centerX;
                }
            }  
            return CGPointMake((proposedContentOffset.x + adjustOffsetX), proposedContentOffset.y)
        }
    
    1. 说完了UICollectionViewFlowLayout,我们要简单模仿分页的功能(滑动效果感官上不如系统的分页)
        func scrollViewDidScroll(scrollView: UIScrollView) {
            if self.lastContentOffset < scrollView.contentOffset.x {
                self.scrollToRight = true
            }
            else{
                self.scrollToRight = false
            }
            self.lastContentOffset = scrollView.contentOffset.x
        }
    

    这里主要是判断向哪边移动

        private var scrollDistance: CGFloat = WIDTH - 84 + 6
        func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
            if self.scrollToRight {
                // 向右移动
                let currentCount = Int(scrollView.contentOffset.x/self.scrollDistance) + 1
                if currentCount == 1 {
                    let totalScrollDistance = self.scrollDistance - self.collection.contentInset.left
                    scrollView.setContentOffset(CGPointMake(CGFloat(currentCount)*totalScrollDistance, 0), animated: true)
                }
                else if currentCount < self.vms.count && currentCount > 1 {
                    let totalScrollDistance = CGFloat(currentCount)*self.scrollDistance - self.collection.contentInset.left
                    scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
                }
                else{
                    let totalScrollDistance = CGFloat(self.vms.count - 1)*self.scrollDistance - self.collection.contentInset.left
                    scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
                }
            }
            else {
                // 向左移动
                let currentCount = Int(scrollView.contentOffset.x/self.scrollDistance)
                let totalScrollDistance = CGFloat(currentCount)*self.scrollDistance - self.collection.contentInset.left
                scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
            }
        }
    
    

    scrollDistance:代表一个Cell的width和Cell间距的和
    这里有一个值注意的点:
    移动第一个cell的时候,只需要移动scrollDistance减去contentInset.left的距离,因为contentInset.left的距离不计算在scrollView.contentOffset.x里面
    移动第一个之外的cell的话,就是要移动一个scrollDistance的距离了

        func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
            let cell = self.collection.cellForItemAtIndexPath(NSIndexPath.init(forItem: self.currentRow, inSection: 0)) as? XXCell
            cell?.timer?.invalidate()
            
            // compute currentRow
            let distanceWithoutFirstRow = scrollView.contentOffset.x - (self.scrollDistance - self.collection.contentInset.left)
            if distanceWithoutFirstRow < 0 {
                self.currentRow = 0
            }
            else{
                self.currentRow = Int(distanceWithoutFirstRow/self.scrollDistance) + 1
            }
            self.loadBottomView(self.currentRow)
        }
    

    这个是滑动结束之后调用的方法,好像是使用了scrollView.setContentOffset才会调用这个方法
    这里我们要计算当前显示在中央的是哪一个Cell,然后再更新与之对应的BottomView的内容。

    实现方式差不多就是这样了,写个文章记录一下!

    相关文章

      网友评论

      • 21175fa2b13d:写的 挺好,就是 翻页的时候 滑动 有滞后感,不知道 作者 现在优化了没有。
        月咏蝴蝶:滑动分页的功能不建议那样做,设置好正确的itemSize就能直接使用系统的分页功能。
      • Jack_Liao:能不能上OC啊大哥
        Jack_Liao:@月咏蝴蝶 好的,谢谢哥。
        月咏蝴蝶:哥,swift和oc差不多,大概知道意思自己码一遍就好
      • 4f6a2617054e:这个有没有Demo地址
        Jack_Liao:弄出来好嘛骚年😄
        4f6a2617054e:@月咏蝴蝶 好的 还是要多谢分享
        月咏蝴蝶:@渣渣_丹 不好意思噢,这个是公司的项目,我没把功能独立出来弄成demo

      本文标题:UICollectionViewFlowLayout实现放大缩小

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