手把手教你自定義流水布局寫特效

最終效果

步驟思路
1.寫出基本的colletionView垄开,讓他進行水平滑動恰响,一個高度只能顯示一個cell
2.了解基本的UICollectionViewLayoutAttributes屬性,讓他可以改變一些cell的屬性芙扎,例如aphelia星岗,和縮小和放大
3.了解一個方法,當(dāng)我們rect改變的時候戒洼,會判斷是否刷新cell的布局
4.判斷哪個cell離colleview的中點最近俏橘,了解一個target函數(shù)
4.1使用剛才rect方法,調(diào)用super方法圈浇,獲取的是計算好餓cell的中心點敷矫,所以知道了他的中心點,如果使用的是self汉额,那么會在調(diào)用一次曹仗,比較麻煩
4.2 計算collviontView的中心點值(注意,不能像過去的那個計算 過去:偏移量(手松開的哪一個可)+寬度的一般),此時拿到的偏移量事不準(zhǔn)的蠕搜,因為我們刺客還有速度怎茫,應(yīng)該拿到最后的偏移量,也就是propertyTargetPoint,將要去哪里9旄颉C巯堋!
4.3祥山,要知道手松開的那一刻圃验,的offSet是不準(zhǔn)的,應(yīng)該獲取最終的缝呕,將要去的位置澳窑,所以傳遞rect是不能下傳遞,而是將來結(jié)束的時候供常,rect摊聋,用x最難判斷,就是propertyPointOffset.x
4.4 遍歷布局屬性栈暇,獲取那個距離最短麻裁,所有的都偏移這段多
4.5 明確,其實最后的所有的偏移量源祈,= 最小的間距 + 目標(biāo)偏移量煎源!但是“最小的間距”可能是正負(fù)數(shù)!香缺!
5.給cell的初始化和結(jié)束設(shè)置一個sectionInset
6.了解prepare函數(shù)的使用


1.寫出基本的colletionView手销,讓他進行水平滑動,一個高度只能顯示一個cell

直接去寫一個colletionView放在vc上就好赫悄,保證colletionView上只有一行原献,設(shè)置數(shù)據(jù)源和代理方法,布局對象直接使用系統(tǒng)的流水布局

控制器成為了代理方法和數(shù)據(jù)源方法埂淮,用extension來寫代理和數(shù)據(jù)源方法姑隅,突然紅色,一臉懵逼倔撞,后來才發(fā)現(xiàn)讲仰,原來是在這里,沒有寫數(shù)據(jù)源必要的方法痪蝇,寫完方法就好了
通過系統(tǒng)的寫出這個樣式
2.了解基本的UICollectionViewLayoutAttributes屬性

每個cell都有尺寸鄙陡,位置和aplha值等等,其實每個cell都有一個UICollectionViewLayoutAttributes屬性躏啰,這里有cell所有的信息趁矾,包括剛剛說的三個,還有很多~~

    /**
     
          解釋類:UICollectionViewLayoutAttributes 
     
         *  每一個cell的尺寸给僵,位置等各種屬性都對應(yīng)這個一個UICollectionViewLayoutAttributes毫捣,這個類中含有很多自己的屬性详拙,改變這個屬性,那么cell的大小或者位置也會發(fā)生變化.還有`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這個控件
         */

想寫出本文的目標(biāo)效果蔓同,我們必須要重寫布局饶辙,自定義一個布局,但是繼承那個比較好斑粱,有兩個選擇弃揽,

  • 一個是抽象類UICollectionViewLayout,繼承這個则北,那么我們滑動都不行矿微,要重寫的東西特別多,特別費事咒锻,不建議
  • 還有就是UICollectionViewFlowLayout這個蘋果已經(jīng)給我計算好了很多的東西冷冗,可以拿來就用守屉,改改基本的某個屬性就行
2.1 自定義一個layout布局SFLayout

直接在項目中將系統(tǒng)的替換惑艇,效果應(yīng)該是一樣的~

2.2 func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?這個函數(shù)是干啥的?

剛才都說了拇泛,每個cell都有一個UICollectionViewLayoutAttributes屬性滨巴,這個函數(shù)的意思是,在rect范圍之內(nèi)的cell的屬性放到一個數(shù)組中俺叭,傳遞出來恭取,一定要調(diào)用super,然后用一個數(shù)組承接熄守,為什么用super蜈垮?因為super是流水布局,返回來的數(shù)組是計算好的,可以微調(diào)使用

    /**
     打印出當(dāng)前rect之內(nèi)的所有cell的“布局屬性”- UICollectionViewLayoutAttributes(返回的是一個數(shù)組)
     */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //獲得super已經(jīng)計算好的尺寸
        let array = super.layoutAttributesForElementsInRect(rect)
        
        //對于每個屬性進行尺寸裕照,位置的微調(diào)
        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.了解一個方法攒发,當(dāng)我們rect改變的時候,會判斷是否刷新cell的布局
3.1 我們看到的這些item都沒有變化~

我們滑動的時候晋南,item一直沒有發(fā)生變化惠猿,但是我想調(diào)用
layoutAttributesForElementsInRect(默認(rèn)之調(diào)用一次)刷新一下內(nèi)部,如何處理负间?

重寫一個方法,只有滑動colletionView偶妖,rect發(fā)生了變化,就會調(diào)用布局屬性方法

    /**
     當(dāng)collectionView顯示的rect發(fā)生了額變化政溃,詢問一下趾访,是否要去刷新所在cell的layoutAttribute,返回true董虱,調(diào)用layoutAttributesForElementsInRect刷新
     */
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        return true
    }
3.2 如何讓放大的倍數(shù)會根據(jù)距離來變化?

這個相對來說有點不好理解扼鞋,不過多看兩遍就好了~

結(jié)構(gòu)圖

1.要注意藍色的是frame,也就是rect
2.綠色的是contentSize我們現(xiàn)在的size肯定是大于frame的
3.黑色的永遠在frame的中點,挺好了,是frame的中點藏鹊,隨著content offset的改變(綠色的view一直前進或者后退)润讥,黑線還在frame 的中點,但是他在綠色的view的x值一直發(fā)生變化
4.紅色的是cell
5.黃色的是cell的中心


思路解析
1.黑色線的位置如何計算盘寡?
黑線.x = contentOffset.x + frame.size.width*0.5
2.黃線如何計算楚殿?
attir.center.x
3.如何就算黃線和黑線的間距?
用間距絕對值就行 abs(黑線.x - atria.center.x)
4.如何根據(jù)絕對值改變cell的大小竿痰?
比例 = 間距絕對值/frame.width 脆粥,除以寬度,是隨便取得數(shù)據(jù)影涉,可以是任意的变隔,但是一定要大于間距的絕對值,我們要比例一定是(0,1)之間的蟹倾,但是隨著間距的變大匣缘,那么比例越來越大啊,咋整鲜棠,只要用 真實比例 =(1-比例)就行肌厨,也可以用1.2- 比例這個隨意,看具體的效果就好了豁陆,根據(jù)你們的要求柑爸,看看到底調(diào)到幾

    /**
     打印出當(dāng)前rect之內(nèi)的所有cell的“布局屬性”- UICollectionViewLayoutAttributes(返回的是一個數(shù)組)
     */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //獲得super已經(jīng)計算好的尺寸
        let array = super.layoutAttributesForElementsInRect(rect)
        
        //對于每個屬性進行尺寸,位置的微調(diào)
        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停止應(yīng)該在什么位置的函數(shù)
2.找出rect中那個一個cell里黑線最近
3.獲取那個cell里黑線最近的距離表鳍,可能是正數(shù)也可能是負(fù)數(shù)
4.讓所有的cell都微調(diào) 合適的間距,最終讓那個特定的cell在屏幕中間

/**
     當(dāng)結(jié)束后祥诽,cell應(yīng)該停止的位置
     
     :param: proposedContentOffset 打算停止的位置(通過速度計算出來的)
     :param: velocity              速度
     
     :returns: 最后停止的位置譬圣,你可能重新給數(shù)據(jù)了,就按照你給的位置停止原押,如果沒有重寫這個方法胁镐,那么就會返回proposedContentOffset這個位置(相對于當(dāng)前的位置)
     */
    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        return CGPointZero
    }

4.1.當(dāng)結(jié)束后,cell應(yīng)該停止的位置,正常cell停止的位置是
proposedContentOffset,如果你重寫了诸衔,那么cell停止后的位置就是你返回的位置

返回的是CGPointZero

4.2 找出rect中那個一個cell里黑線最近
求間距盯漂,還用剛才的那個公式用間距絕對值就行 abs(黑線.x - attri.center.x)行不行,為什么笨农?
答案:不行就缆,公式中獲取的cell的中心點是可控的,我們知道谒亦,那一時刻竭宰,必須的空郊,但是為什么在本函數(shù)中,我們認(rèn)為他不行切揭,不是準(zhǔn)確的值狞甚?,因為我們快速滑動廓旬,然后松手哼审,他還有速度,根據(jù)慣性孕豹,他還要滑動一會涩盾,所以不能取此時此刻的黃線(cell的中點),所以我們的公式是 本函數(shù)返回值 = proposedContentOffset .x + 黃線和黑線最近的間距(可能正也可能負(fù))


首先在本方法中励背,應(yīng)該獲取到rect中的cell的那些屬性(layoutAttributesForElementsInRect)春霍,如何獲取那個數(shù)組?
調(diào)用super.layoutAttributesForElementsInRect方法叶眉,可以輕松獲取的是計算好的cell的中心點址儒。
如果使用的是self.layoutAttributesForElementsInRect,獲取的數(shù)據(jù)是經(jīng)過我們計算的竟闪,我們此時要拿到未經(jīng)過計算的attir數(shù)組

       //獲取將要顯示rect里面的cell屬性
        let attrs = super.layoutAttributesForElementsInRect(rect)

獲取完了數(shù)組离福,我們拿數(shù)組中的對象和中線比較一下杖狼,看看那個是最近的炼蛤,做一個記錄,然后讓所有的返回都是本函數(shù)返回值 = proposedContentOffset .x + 黃線和黑線最近的間距(可能正也可能負(fù))

    /**
     當(dāng)結(jié)束后蝶涩,cell應(yīng)該停止的位置
     
     :param: proposedContentOffset 打算停止的位置(通過速度計算出來的)
     :param: velocity              速度
     
     :returns: 最后停止的位置理朋,你可能重新給數(shù)據(jù)了,就按照你給的位置停止绿聘,如果沒有重寫這個方法嗽上,那么就會返回proposedContentOffset這個位置(相對于當(dāng)前的位置)
     */
    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
//        print(proposedContentOffset)
        
        //1.獲取所有的布局屬性,調(diào)用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可能是正負(fù))
        
        var currentOffset = proposedContentOffset
        currentOffset.x += CGFloat(margin)

        return currentOffset
    }

4.3 計算collviontView的中心點值(注意熄攘,不能像過去的那個計算 過去:偏移量(手松開的哪一個可)+寬度的一般),此時拿到的偏移量事不準(zhǔn)的兽愤,因為我們刺客還有速度,應(yīng)該拿到最后的偏移量挪圾,也就是propertyTargetPoint浅萧,將要去哪里!U芩肌洼畅!
4.3,要知道手松開的那一刻棚赔,的offSet是不準(zhǔn)的帝簇,應(yīng)該獲取最終的徘郭,將要去的位置,所以傳遞rect是不能下傳遞丧肴,而是將來結(jié)束的時候残揉,rect,用x最難判斷芋浮,就是propertyPointOffset.x
4.4 遍歷布局屬性冲甘,獲取那個距離最短,所有的都偏移這段多
4.5 明確途样,其實最后的所有的偏移量江醇,= 最小的間距 + 目標(biāo)偏移量!但是“最小的間距”可能是正負(fù)數(shù):蜗尽陶夜!


5.給cell的初始化和結(jié)束設(shè)置一個sectionInset ,了解prepare函數(shù)的使用

基本都講完了,但是剛剛啟動程序的時候裆站,0號cell里左邊太近了条辟,不好看,我們想第一個cell和最后一個cell都在屏幕中間宏胯,怎么辦羽嫡?
給他sectionInset設(shè)置數(shù)據(jù)就好了,組間距

最終效果
    /**
     用來做布局的初始化肩袍,不建議在init中調(diào)用杭棵,因為那時候的colletionView = nil
     */
    override func prepareLayout() {
        super.prepareLayout()
        //設(shè)置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地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魂爪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子艰管,更是在濱河造成了極大的恐慌滓侍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲芋,死亡現(xiàn)場離奇詭異撩笆,居然都是意外死亡,警方通過查閱死者的電腦和手機缸浦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門夕冲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人餐济,你說我怎么就攤上這事耘擂。” “怎么了絮姆?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵醉冤,是天一觀的道長秩霍。 經(jīng)常有香客問我,道長蚁阳,這世上最難降的妖魔是什么铃绒? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮螺捐,結(jié)果婚禮上颠悬,老公的妹妹穿的比我還像新娘。我一直安慰自己定血,他們只是感情好赔癌,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著澜沟,像睡著了一般灾票。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茫虽,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天刊苍,我揣著相機與錄音,去河邊找鬼濒析。 笑死正什,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的号杏。 我是一名探鬼主播婴氮,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馒索!你這毒婦竟也來了莹妒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绰上,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渠驼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜈块,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年迷扇,在試婚紗的時候發(fā)現(xiàn)自己被綠了百揭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜓席,死狀恐怖器一,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情厨内,我是刑警寧澤祈秕,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布渺贤,位于F島的核電站,受9級特大地震影響请毛,放射性物質(zhì)發(fā)生泄漏志鞍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一方仿、第九天 我趴在偏房一處隱蔽的房頂上張望固棚。 院中可真熱鬧,春花似錦仙蚜、人聲如沸此洲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黍翎。三九已至,卻和暖如春艳丛,著一層夾襖步出監(jiān)牢的瞬間匣掸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工氮双, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碰酝,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓戴差,卻偏偏與公主長得像送爸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暖释,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容