UICollectionViewFlowLayout实现放大缩小动画效果的左右滑动

好久没写过文章了,趁现在得空写一下最近实现的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的内容。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,322评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,288评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,227评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,015评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,936评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,534评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,995评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,616评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,907评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,923评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,741评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,525评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,016评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,141评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,453评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,054评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,249评论 2 339

推荐阅读更多精彩内容