手把手教你自定义流水布局写特效

最终效果

步骤思路
1.写出基本的colletionView,让他进行水平滑动,一个高度只能显示一个cell
2.了解基本的UICollectionViewLayoutAttributes属性,让他可以改变一些cell的属性,例如aphelia,和缩小和放大
3.了解一个方法,当我们rect改变的时候,会判断是否刷新cell的布局
4.判断哪个cell离colleview的中点最近,了解一个target函数
4.1使用刚才rect方法,调用super方法,获取的是计算好饿cell的中心点,所以知道了他的中心点,如果使用的是self,那么会在调用一次,比较麻烦
4.2 计算collviontView的中心点值(注意,不能像过去的那个计算 过去:偏移量(手松开的哪一个可)+宽度的一般),此时拿到的偏移量事不准的,因为我们刺客还有速度,应该拿到最后的偏移量,也就是propertyTargetPoint,将要去哪里!!!
4.3,要知道手松开的那一刻,的offSet是不准的,应该获取最终的,将要去的位置,所以传递rect是不能下传递,而是将来结束的时候,rect,用x最难判断,就是propertyPointOffset.x
4.4 遍历布局属性,获取那个距离最短,所有的都偏移这段多
4.5 明确,其实最后的所有的偏移量,= 最小的间距 + 目标偏移量!但是“最小的间距”可能是正负数!!
5.给cell的初始化和结束设置一个sectionInset
6.了解prepare函数的使用


1.写出基本的colletionView,让他进行水平滑动,一个高度只能显示一个cell

直接去写一个colletionView放在vc上就好,保证colletionView上只有一行,设置数据源和代理方法,布局对象直接使用系统的流水布局

控制器成为了代理方法和数据源方法,用extension来写代理和数据源方法,突然红色,一脸懵逼,后来才发现,原来是在这里,没有写数据源必要的方法,写完方法就好了
通过系统的写出这个样式
2.了解基本的UICollectionViewLayoutAttributes属性

每个cell都有尺寸,位置和aplha值等等,其实每个cell都有一个UICollectionViewLayoutAttributes属性,这里有cell所有的信息,包括刚刚说的三个,还有很多~~

    /**
     
          解释类:UICollectionViewLayoutAttributes 
     
         *  每一个cell的尺寸,位置等各种属性都对应这个一个UICollectionViewLayoutAttributes,这个类中含有很多自己的属性,改变这个属性,那么cell的大小或者位置也会发生变化.还有`transform `可以更改他的形变等~
         
         @property (nonatomic) CGRect frame;
         @property (nonatomic) CGPoint center;
         @property (nonatomic) CGSize size;
         @property (nonatomic) CATransform3D transform3D;
         @property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
         @property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
         @property (nonatomic) CGFloat alpha;
         @property (nonatomic) NSInteger zIndex; // default is 0
         @property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
         @property (nonatomic, strong) NSIndexPath *indexPath;
         在这里还能拿到collectionView这个控件
         */

想写出本文的目标效果,我们必须要重写布局,自定义一个布局,但是继承那个比较好,有两个选择,

  • 一个是抽象类UICollectionViewLayout,继承这个,那么我们滑动都不行,要重写的东西特别多,特别费事,不建议
  • 还有就是UICollectionViewFlowLayout这个苹果已经给我计算好了很多的东西,可以拿来就用,改改基本的某个属性就行
2.1 自定义一个layout布局SFLayout

直接在项目中将系统的替换,效果应该是一样的~

2.2 func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?这个函数是干啥的?

刚才都说了,每个cell都有一个UICollectionViewLayoutAttributes属性,这个函数的意思是,在rect范围之内的cell的属性放到一个数组中,传递出来,一定要调用super,然后用一个数组承接,为什么用super?因为super是流水布局,返回来的数组是计算好的,可以微调使用

    /**
     打印出当前rect之内的所有cell的“布局属性”- UICollectionViewLayoutAttributes(返回的是一个数组)
     */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //获得super已经计算好的尺寸
        let array = super.layoutAttributesForElementsInRect(rect)
        
        //对于每个属性进行尺寸,位置的微调
        for index in 0 ..< array!.count
        {
            let  abc:UICollectionViewLayoutAttributes  = array![index]
// 1.改变allah值
      //      abc.alpha = 0.2

//2.改变了大小
//            let scale:CGFloat = CGFloat(arc4random_uniform(345))/345.0
//            abc.transform = CGAffineTransformMakeScale(scale, scale)   
        }
        return array
abc.alpha = 0.2
改变了大小-随机变化

3.了解一个方法,当我们rect改变的时候,会判断是否刷新cell的布局
3.1 我们看到的这些item都没有变化~

我们滑动的时候,item一直没有发生变化,但是我想调用
layoutAttributesForElementsInRect(默认之调用一次)刷新一下内部,如何处理?

重写一个方法,只有滑动colletionView,rect发生了变化,就会调用布局属性方法

    /**
     当collectionView显示的rect发生了额变化,询问一下,是否要去刷新所在cell的layoutAttribute,返回true,调用layoutAttributesForElementsInRect刷新
     */
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        return true
    }
3.2 如何让放大的倍数会根据距离来变化?

这个相对来说有点不好理解,不过多看两遍就好了~

结构图

1.要注意蓝色的是frame,也就是rect
2.绿色的是contentSize我们现在的size肯定是大于frame的
3.黑色的永远在frame的中点,挺好了,是frame的中点,随着content offset的改变(绿色的view一直前进或者后退),黑线还在frame 的中点,但是他在绿色的view的x值一直发生变化
4.红色的是cell
5.黄色的是cell的中心


思路解析
1.黑色线的位置如何计算?
黑线.x = contentOffset.x + frame.size.width*0.5
2.黄线如何计算?
attir.center.x
3.如何就算黄线和黑线的间距?
用间距绝对值就行 abs(黑线.x - atria.center.x)
4.如何根据绝对值改变cell的大小?
比例 = 间距绝对值/frame.width ,除以宽度,是随便取得数据,可以是任意的,但是一定要大于间距的绝对值,我们要比例一定是(0,1)之间的,但是随着间距的变大,那么比例越来越大啊,咋整,只要用 真实比例 =(1-比例)就行,也可以用1.2- 比例这个随意,看具体的效果就好了,根据你们的要求,看看到底调到几

    /**
     打印出当前rect之内的所有cell的“布局属性”- UICollectionViewLayoutAttributes(返回的是一个数组)
     */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //获得super已经计算好的尺寸
        let array = super.layoutAttributesForElementsInRect(rect)
        
        //对于每个属性进行尺寸,位置的微调
        for index in 0 ..< array!.count
        {
            let  abc:UICollectionViewLayoutAttributes  = array![index]
            //1.获取collctionView最中间的线的X值
            let collectionViewX:CGFloat = (collectionView?.contentOffset.x)! + CGFloat((collectionView?.frame.width)!)*0.5
           //2.获取cell的中心点位置
            let cellX = abc.center.x
           let scale =  abs(cellX - collectionViewX)/(collectionView?.frame.width)!
            let trueScale = 1-scale
            abc.transform = CGAffineTransformMakeScale(trueScale, trueScale)
            
        }
        return array
    }
随着远近改变大小
4.判断哪个cell离colleview的中点最近

刚才写完了如何将cell按照比例放大和缩小,还有就是如何放置到中间


思路
1.重写过去cell停止应该在什么位置的函数
2.找出rect中那个一个cell里黑线最近
3.获取那个cell里黑线最近的距离,可能是正数也可能是负数
4.让所有的cell都微调 合适的间距,最终让那个特定的cell在屏幕中间

/**
     当结束后,cell应该停止的位置
     
     :param: proposedContentOffset 打算停止的位置(通过速度计算出来的)
     :param: velocity              速度
     
     :returns: 最后停止的位置,你可能重新给数据了,就按照你给的位置停止,如果没有重写这个方法,那么就会返回proposedContentOffset这个位置(相对于当前的位置)
     */
    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        return CGPointZero
    }

4.1.当结束后,cell应该停止的位置,正常cell停止的位置是
proposedContentOffset,如果你重写了,那么cell停止后的位置就是你返回的位置

返回的是CGPointZero

4.2 找出rect中那个一个cell里黑线最近
求间距,还用刚才的那个公式用间距绝对值就行 abs(黑线.x - attri.center.x)行不行,为什么?
答案:不行,公式中获取的cell的中心点是可控的,我们知道,那一时刻,必须的,但是为什么在本函数中,我们认为他不行,不是准确的值?,因为我们快速滑动,然后松手,他还有速度,根据惯性,他还要滑动一会,所以不能取此时此刻的黄线(cell的中点),所以我们的公式是 本函数返回值 = proposedContentOffset .x + 黄线和黑线最近的间距(可能正也可能负)


首先在本方法中,应该获取到rect中的cell的那些属性(layoutAttributesForElementsInRect),如何获取那个数组?
调用super.layoutAttributesForElementsInRect方法,可以轻松获取的是计算好的cell的中心点。
如果使用的是self.layoutAttributesForElementsInRect,获取的数据是经过我们计算的,我们此时要拿到未经过计算的attir数组

       //获取将要显示rect里面的cell属性
        let attrs = super.layoutAttributesForElementsInRect(rect)

获取完了数组,我们拿数组中的对象和中线比较一下,看看那个是最近的,做一个记录,然后让所有的返回都是本函数返回值 = proposedContentOffset .x + 黄线和黑线最近的间距(可能正也可能负)

    /**
     当结束后,cell应该停止的位置
     
     :param: proposedContentOffset 打算停止的位置(通过速度计算出来的)
     :param: velocity              速度
     
     :returns: 最后停止的位置,你可能重新给数据了,就按照你给的位置停止,如果没有重写这个方法,那么就会返回proposedContentOffset这个位置(相对于当前的位置)
     */
    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
//        print(proposedContentOffset)
        
        //1.获取所有的布局属性,调用super的方法获取
        //获取将要显示的rect值
        let rX:CGFloat = proposedContentOffset.x
        let rect = CGRectMake(rX, 0, (collectionView?.frame.width)!, (collectionView?.frame.height)!)
       //获取将要显示rect里面的cell属性
        let attrs = super.layoutAttributesForElementsInRect(rect)
        
        //计算colletionView中间哪个线的x
        let centX:CGFloat = proposedContentOffset.x + (collectionView?.frame.width)!*0.5
        
        //保存最小的间距
        var margin = MAXFLOAT
        //2.遍历属性,获取最小的间距
        for  index  in 0 ..< (attrs?.count)! {
            let att = attrs![index]
            let op = Float(att.center.x - centX)
            if abs(margin) > abs(op)
            {
                margin = Float(att.center.x - centX)
            }
        }
        
        
        //所有的cell都要偏移量 = proposedContentOffset.x + marign(margin可能是正负)
        
        var currentOffset = proposedContentOffset
        currentOffset.x += CGFloat(margin)

        return currentOffset
    }

4.3 计算collviontView的中心点值(注意,不能像过去的那个计算 过去:偏移量(手松开的哪一个可)+宽度的一般),此时拿到的偏移量事不准的,因为我们刺客还有速度,应该拿到最后的偏移量,也就是propertyTargetPoint,将要去哪里!!!
4.3,要知道手松开的那一刻,的offSet是不准的,应该获取最终的,将要去的位置,所以传递rect是不能下传递,而是将来结束的时候,rect,用x最难判断,就是propertyPointOffset.x
4.4 遍历布局属性,获取那个距离最短,所有的都偏移这段多
4.5 明确,其实最后的所有的偏移量,= 最小的间距 + 目标偏移量!但是“最小的间距”可能是正负数!!


5.给cell的初始化和结束设置一个sectionInset ,了解prepare函数的使用

基本都讲完了,但是刚刚启动程序的时候,0号cell里左边太近了,不好看,我们想第一个cell和最后一个cell都在屏幕中间,怎么办?
给他sectionInset设置数据就好了,组间距

最终效果
    /**
     用来做布局的初始化,不建议在init中调用,因为那时候的colletionView = nil
     */
    override func prepareLayout() {
        super.prepareLayout()
        //设置sectionInset
        let magin:CGFloat =  ((collectionView?.frame.width)! - itemSize.width) * 0.5
        sectionInset = UIEdgeInsetsMake(0, magin, 0, magin)
    }

6.自顶一个有imageView的cell就好了~

同xib加载的,所以注册的时候有点不同,是这样的


        //注册cell
       collectionView.registerNib(UINib.init(nibName: "SFImageCell", bundle: nil),
                                  forCellWithReuseIdentifier: sfCellIdent)
最终效果

demo地址

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

推荐阅读更多精彩内容