本篇文章主要是書《UICOllectionView The Complete Guide》的讀書筆記,對(duì)比iCrousel源碼,并用UICollectionView來(lái)實(shí)現(xiàn)iCrousel提供的部分的卡片效果众旗。前段時(shí)間做了一個(gè)左右滑動(dòng)界面的需求又厉,用的是三個(gè)UICollectionView相互嵌套來(lái)完成了,在做的過(guò)程中同事說(shuō)可以使用iCrousel來(lái)實(shí)現(xiàn),但是對(duì)iCrousel不太熟悉败晴,最終我還是使用的是UICollectionView,先完成任務(wù)之后開始研究iCrousel栽渴。結(jié)合著這個(gè)過(guò)程中遇到的問(wèn)題尖坤,自己把iCrousel的源碼看了一遍,為了看的懂源碼闲擦,中間有看了一本《Core Animation Advanced Techniques》慢味。因?yàn)檫@本書已經(jīng)有大神翻譯好了,有時(shí)候原版看著費(fèi)勁的時(shí)候就對(duì)著翻譯看墅冷,但是在看《UICollectionView The Complete Guide》的時(shí)候就沒(méi)有這么幸運(yùn)了纯路,在網(wǎng)上一直沒(méi)有找到譯本,只能看原版寞忿。還記得需求是8月初的時(shí)候做的驰唬,當(dāng)我學(xué)完相關(guān)知識(shí)的時(shí)候已經(jīng)到了十一月底了,當(dāng)然中間還有些公司的任務(wù)要開發(fā)腔彰,中途還看了一些其他的書籍叫编,想著如果文筆好的話還能寫個(gè)雞湯類的讀書筆記啥的,但是寫了點(diǎn)東西才知道霹抛,自己能把一個(gè)技術(shù)點(diǎn)寫清楚已經(jīng)很費(fèi)勁了搓逾,還是要繼續(xù)學(xué)習(xí)啊~
趁著剛剛看完,做一個(gè)讀書筆記杯拐,之后就不用再去翻書找知識(shí)點(diǎn)了霞篡。
概述
看了下UICollectionView,覺(jué)的這個(gè)是一個(gè)有點(diǎn)像RN的組件端逼,內(nèi)容和布局完全分離的設(shè)計(jì)朗兵,UICollectionView負(fù)責(zé)界面部分,UICollectionViewlayout負(fù)責(zé)UIcollectionView的布局裳食,具體的每個(gè)元素的布局就交給UICollectionViewLayoutAttributes,另外attributes也是可以進(jìn)行擴(kuò)展的矛市,比如需要加入maskView或者改變layer的屬性芙沥,都可以在attributes里面進(jìn)行自己的定義诲祸。
iCrousel的主要的實(shí)現(xiàn)方式是將view加載到視圖的中間,然后根據(jù)設(shè)定好的幾種展示風(fēng)格設(shè)計(jì)好transform3D而昨。比較意外的是沒(méi)有使用UIScrollview來(lái)實(shí)現(xiàn)視圖的滾動(dòng)救氯,而是使用逐幀動(dòng)畫的原理,根據(jù)ios設(shè)備屏幕的刷新速度來(lái)計(jì)算每一幀的所有view的位置歌憨。ios設(shè)備的刷新頻率是60hz着憨,也就是說(shuō)一秒鐘60次,結(jié)合動(dòng)畫一秒24幀就不會(huì)感覺(jué)卡頓务嫡,難怪動(dòng)畫會(huì)這么流暢甲抖。
因?yàn)閁ICollectionView是UIScrollView的子類漆改,而iCrousel是計(jì)算每個(gè)item的偏移,所以實(shí)現(xiàn)方式上也是有很大的差異准谚,iCrousel需要手動(dòng)計(jì)算挫剑,而UICollectionView可以直接使用UIScrollview帶來(lái)的便利。
重用問(wèn)題
在UICollectionView中重用的控件有三種柱衔,分別是UICollectionViewCell樊破、SupplementatyView和DecorationView,其中UICollectionViewCell就不用多說(shuō)了唆铐,SupplementaryView指的是header和footer哲戚。DecorationView指的是裝飾圖,既然是裝飾圖艾岂,那么應(yīng)該是獨(dú)立于view的顺少,所以是在layout里面進(jìn)行設(shè)置的。這兩個(gè)view是繼承UICollectionReusableView的王浴,所以UICollectionView幫我們都做好了重用祈纯。
從iCrousel中可以大概猜測(cè)到重用的機(jī)制是怎么實(shí)現(xiàn)的。在iCrousel里面維護(hù)了兩個(gè)隊(duì)列叼耙,一個(gè)是屏幕上可見items的隊(duì)列腕窥,另一個(gè)是不可見的items的隊(duì)列,也就是重用隊(duì)列筛婉。實(shí)時(shí)的更新兩個(gè)隊(duì)列簇爆,每次先檢查重用隊(duì)列里面有沒(méi)有可以重復(fù)使用的item,如果有的話就任取一個(gè)拿來(lái)使用爽撒,如果沒(méi)有話的再新建一個(gè)入蛆。
類比到UICollectionView里面的話,自我感覺(jué)應(yīng)該是UICollectionReusableView或者UICollectionViewCell被加入到重用隊(duì)列的時(shí)候會(huì)觸發(fā)一個(gè)prepareForReuse的鉤子方法硕勿,在這個(gè)方法里面我們就可以做一些view被重用的準(zhǔn)備哨毁。
UICollectionView
UICollectionView的用法與UITableView的用法類似,如果是創(chuàng)建一個(gè)簡(jiǎn)單的UICollectionView界面的話源武,那么完全可以像使用UITableView一樣來(lái)使用扼褪。其方法也是實(shí)現(xiàn)類似的dataSource和Delegate。但距離UITableView框架問(wèn)世已經(jīng)過(guò)去了好多年了粱栖,隨著每一代ios系統(tǒng)的升級(jí)话浇,UITableView也帶來(lái)了很多的變化,畢竟有一些局限性闹究。所有就有了UICollectionView幔崖。
UICollectionViewCell
首先看一下UICollectionViewCell的層次結(jié)構(gòu):
從這張圖上我們可以看出來(lái),UICollectionViewCell之上還有還有三個(gè)View,分別是backgroundView和selectedBackgroundView赏寇,最外層的才是contentView吉嫩,從字面上理解就是背景圖層,選中的背景圖層嗅定,最后才是我們編輯的內(nèi)容圖層率挣。所以我們自定義的view應(yīng)該是加上contentView上面才對(duì)。另外我們使用xib文件或者是storyboard文件創(chuàng)建的CollectionViewCell的時(shí)候不會(huì)看到有其他的圖層露戒,甚至看不到像tableview一樣的contentView椒功,是因?yàn)槲覀儎?chuàng)建的view就是默認(rèn)添加到contentView上面的。所以用代碼來(lái)構(gòu)建頁(yè)面的時(shí)候要注意添加到contentView上面來(lái)智什,這樣才不會(huì)破壞UICollectionViewCell的層級(jí)結(jié)構(gòu)动漾,當(dāng)我們使用到選中態(tài)的時(shí)候才不會(huì)發(fā)生不必要的麻煩(現(xiàn)在終于知道了,其實(shí)自己bug多的原因真的是對(duì)知識(shí)的一知半解導(dǎo)致的)荠锭。另外selectedBackgroundView和backgroundView都是option的旱眯,當(dāng)有的時(shí)候才會(huì)加入到view的層級(jí)當(dāng)中。
性能優(yōu)化
在之前core_animation筆記里面有提到tableView加載很多圖片時(shí)的優(yōu)化問(wèn)題证九。后臺(tái)加載圖片删豺,后臺(tái)解壓圖片(png格式比jpeg塊),避免頻繁的離屏渲染等方面來(lái)加載圖片愧怜。另外這本書的前半部分還有使用Instruments來(lái)分析哪個(gè)方法造成了界面的卡頓呀页,界面的卡頓也就說(shuō)掉幀的問(wèn)題,因?yàn)楹臅r(shí)任務(wù)造成沒(méi)有達(dá)到60Hz的渲染拥坛,就會(huì)有卡頓的現(xiàn)象出現(xiàn)蓬蝶。通過(guò)Instruments的Core Animation工具就能分析出哪個(gè)方法耗時(shí)較長(zhǎng),從而定位到問(wèn)題猜惋。
離屏渲染是將渲染好的紋理緩存在GPU丸氛,不需要每次重新渲染,對(duì)高頻率使用的圖層優(yōu)化較大著摔,比如把UICollectionViewCell的shouldRasterize設(shè)置成YES來(lái)觸發(fā)離屏渲染缓窜,這樣在做動(dòng)畫的的時(shí)候不用每一幀都重復(fù)渲染,減少GPU的壓力谍咆。直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū)禾锤,然后渲染到紋理中,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價(jià)很多卧波。因?yàn)檫@其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)时肿,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))。
UICollectionViewLayout
UICollectionViewLayout是一個(gè)沒(méi)有實(shí)現(xiàn)具體方法的類(是否可以叫做抽象類港粱,因?yàn)榇_實(shí)很多方法需要重載才有有效),只有繼承并實(shí)現(xiàn)相關(guān)的方法之后才能夠使用。蘋果的SDK已經(jīng)幫我們實(shí)現(xiàn)了一個(gè)流式布局的類查坪,就是UICollectionViewFlowLayout寸宏,一般的流式布局都是可以通過(guò)繼承這個(gè)類來(lái)實(shí)現(xiàn)。我們可以在flowLayout里面設(shè)置itemSize偿曙,sectionInset氮凝,item間距,item行距等基本的Cell的設(shè)置望忆。設(shè)置好cell的基本屬性之后罩阵,剩下的flowlayout就會(huì)幫我們把布局根據(jù)流布局的規(guī)則計(jì)算出來(lái)了。一般我們使用flowLayout的之類已經(jīng)足夠了启摄。layout管理著UICollectionView的布局稿壁,也就是說(shuō)它管理著界面上能看到的一切,無(wú)非就是UICollectionViewCell歉备,SupplementatyView和DecorationView傅是。每個(gè)元素對(duì)應(yīng)了一個(gè)UICollectionViewLayoutAttributes,通過(guò)這個(gè)管理attributes屬性就能夠改變UICollectionView的布局蕾羊,主要是要實(shí)現(xiàn)以下幾個(gè)方法:
- (NSArray *)layoutAttributesForElements:(Rect)rect;
這個(gè)方法會(huì)返回在這個(gè)rect里面所有的attributes數(shù)組喧笔,可以通過(guò)復(fù)寫這個(gè)方法來(lái)編輯SupplementatyView和DecorationView的attributes屬性,判斷attributes是否為cell的方法就是判斷attributes的representedElementKind屬性,當(dāng)前為nil的時(shí)候就是cell龟再。具體區(qū)分的話就是要使用representedElementCategory屬性了书闸。
這個(gè)方法之后還有針對(duì)每個(gè)indexPath的修改attributes的方法,也是屬于同一種方法類型利凑,具體可以查閱文檔(個(gè)人感覺(jué)文檔已經(jīng)很清楚了梗劫,不過(guò)很多知識(shí)還是需要看一些例子才能將這些零碎的知識(shí)點(diǎn)整合起來(lái))。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
這個(gè)方法是用來(lái)判斷是否CollectionView的bounds發(fā)生變化的時(shí)候就更新attributes截碴,所以當(dāng)我們需要實(shí)時(shí)的更新attributes的時(shí)候梳侨,這個(gè)方法就要返回YES。因?yàn)閁ICollectionView是UIScrollview的子類類日丹,而UIScrollview的實(shí)現(xiàn)方式就是UIView改變自身的bounds從而改變可見的范圍走哺,所以這個(gè)方法就是當(dāng)scrollview發(fā)生偏移的時(shí)候(bounds發(fā)生變化)就將layout無(wú)效,重新計(jì)算layout哲虾。這里還需要注意一個(gè)問(wèn)題丙躏,就是屏幕發(fā)生旋轉(zhuǎn)的時(shí)候bounds也會(huì)發(fā)生改變,并且在旋轉(zhuǎn)的時(shí)候默認(rèn)使用的動(dòng)畫是移除所有items和添加所有items所用動(dòng)畫束凑,所以如果自定義了item出現(xiàn)和消失動(dòng)畫的話就會(huì)在旋轉(zhuǎn)屏幕的時(shí)候?qū)γ恳粋€(gè)item重復(fù)做動(dòng)畫晒旅。解決方案在網(wǎng)上有查到一個(gè)方法,詳見:UICollectionView動(dòng)畫
initialLayoutAttributesForAppearingItemAtIndexPath:
initialLayoutAttributesForAppearingSupplementaryElementOfKind:atIndexPath:
initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
finalLayoutAttributesForDisappearingItemAtIndexPath:
finalLayoutAttributesForDisappearingSupplementaryElementOfKind:atIndexPath:
finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
這些方法就是上面所說(shuō)的定義item消失和添加時(shí)候的動(dòng)畫汪诉,當(dāng)然也可以對(duì)Supplementary view和Decoration view的動(dòng)畫废恋。
還有兩個(gè)方法也很重要谈秫,用來(lái)識(shí)別bounds改變的動(dòng)畫和update的動(dòng)畫,同時(shí)用這兩個(gè)方法可以避免上面所說(shuō)的屏幕旋轉(zhuǎn)動(dòng)畫的異常鱼鼓。
- prepareForCollectionViewUpdates:
- prepareForAnimatedBoundsChange:
第一個(gè)方法是在view更新的時(shí)候觸發(fā)的方法拟烫,第二個(gè)方法是在bounds發(fā)生改變的時(shí)候觸發(fā)的方法,在這兩個(gè)方法里面可以分別申請(qǐng)兩個(gè)數(shù)組來(lái)區(qū)分兩種動(dòng)畫迄本。
UICollectionViewLayoutAttributess
這個(gè)名字比較長(zhǎng)的類似用來(lái)存儲(chǔ)UICollectionView元素布局的屬性硕淑,可以改變frame,bounds嘉赎,center置媳,size,transform公条,transform3D拇囊,alpha,zIndex赃份,hidden等屬性寂拆,其中zIndex反映的是view的層級(jí)關(guān)系,默認(rèn)的zIndex值是0抓韩,大的在層級(jí)的上面纠永,小的在下面。transform3D可以給每個(gè)元素添加transform3D屬性谒拴,這樣的話就可以仿照iCrousel來(lái)實(shí)現(xiàn)相似的效果了尝江。
自定義UICollectionViewLayoutAttributess屬性
對(duì)于繼承于UICollectionViewLayoutAttributess的子類,我們可以自定義一些items需要表現(xiàn)的屬性英上,比如是否光柵化炭序,自定義maskView的透明度之類的我們自定義items或者Supplementary和Decoration的表現(xiàn)。比如說(shuō)我要根據(jù)偏移來(lái)計(jì)算不同的maskView 透明度的變化苍日,那么就要給UICollectionViewLayoutAttributess子類加上屬性alpha惭聂,同時(shí)要復(fù)寫copy和equal方法。對(duì)于自定義的屬性相恃,我們?cè)趯?shí)現(xiàn)itemCell的時(shí)候需要復(fù)寫方法 -applyLayoutAttributes: 辜纲,在這個(gè)方法里面可以接受作用于這個(gè)itemCell的attributes,對(duì)于我們自定義的屬性拦耐,需要在這個(gè)方法里面讓itemCell根據(jù)屬性做一些相應(yīng)的改變耕腾。
關(guān)于光柵化會(huì)觸發(fā)離屏渲染,上面也有所離屏渲染會(huì)帶來(lái)很多開銷杀糯,但是對(duì)于cell圖層比較多扫俺,合成起來(lái)比較費(fèi)時(shí),并且頻繁重用的view來(lái)說(shuō)固翰,進(jìn)行離屏渲染是很有幫助的狼纬。
小結(jié)
關(guān)于UICollectionView的知識(shí)點(diǎn)大約就是這么多羹呵,但是具體要實(shí)現(xiàn)哪些動(dòng)畫還是需要我們自己來(lái)實(shí)現(xiàn)相應(yīng)的算法。相比于iCrousel畸颅,UICollectionView已經(jīng)實(shí)現(xiàn)好了重用機(jī)制担巩,并且將layout的代碼獨(dú)立出來(lái)方援,也是使用transform3D屬性來(lái)改變item的位置没炒,所以用UICollectionView實(shí)現(xiàn)iCrousel的布局方式只需要將其對(duì)每個(gè)item的transform3D算法轉(zhuǎn)移到item對(duì)應(yīng)的attributes里面就可以了。