前言:
實現(xiàn)垂直方向的單列表來說袁滥,使用UITableView足以;若是需要構(gòu)建橫向滑動列表灾螃、gridView等直線型布局题翻,則使用UICollectionView+UICollectionViewFlowLayout搭建最合適;更復(fù)雜的布局腰鬼,則可以使用UICollectionView+自定義Layout來實現(xiàn)嵌赠。
文章的最后,筆者將會貼出使用自定義的UICollectionViewLayout實現(xiàn)的比較炫酷的動畫熄赡,代碼并非筆者原創(chuàng)姜挺,而是來自琿少,實現(xiàn)的思路,讀者可參考原文彼硫。
demo已上傳到github,方便讀者下載炊豪;
譯文:
UICollectionViewLayout是一個應(yīng)該子類化的抽象基類,用來生成collection view的布局信息拧篮。 布局對象決定cell
, 追加視圖
(supplementary views), 和 裝飾視圖
(decoration views) 在collection view內(nèi)部的位置词渤,并在 collection view 獲取的時候提供這些布局信息。collection view將把這些布局信息應(yīng)用到相應(yīng)的視圖上以便在屏幕上進(jìn)行展示他托。
如果想使用UICollectionViewLayout掖肋,你必須把它子類化。在子類化UICollectionViewLayout之前赏参,應(yīng)該先了解UICollectionViewFlowLayout志笼,看看它是否已經(jīng)能夠滿足你的布局需求。(注:UICollectionViewFlowLayout是官方提供的子類化的UICollectionViewLayout把篓,能滿足絕大部分需求)
子類化注意事項:
布局對象(layout object)主要用來為collection view的元素提供位置信息和狀態(tài)信息(比如:選中狀態(tài)纫溃,未選中狀態(tài)等)。布局對象并不負(fù)責(zé)創(chuàng)建視圖韧掩,這些視圖是collection view的數(shù)據(jù)源(data source)創(chuàng)建的紊浩。布局對象只不過定義了這些視圖的位置和大小。
Collection view有三種需要布局的視圖元素疗锐。
- Cells:
cell是布局中的主要元素坊谁,在collection中每一個cell代表了一個單獨的數(shù)據(jù)項霉囚。一個collection view 既可能只有唯一的一組cell臊诊,也可能把這一組cell分到多個 段(section)。布局對象的主要功能就是在 collection view的內(nèi)容區(qū)對這些cell進(jìn)行布局 - supplementary views(追加視圖)
追加視圖代表和cell完全不同的數(shù)據(jù)菩收。補充視圖不支持選中非選中狀態(tài)雇卷,可以通過追加視圖添加某個段的段頭和段尾鬓椭,或者整個collection view的段頭和段尾颠猴。追加視圖不是必選項,即實不實現(xiàn)都可以小染。追加視圖的使用和布局也是由布局對象決定的翘瓮。 - Decoration views(裝飾視圖)
裝飾視圖是用來裝飾的視圖,不支持選中非選中狀態(tài)裤翩,和數(shù)據(jù)也沒有內(nèi)在的關(guān)聯(lián)(不像cell和section需要返回個數(shù))资盅。裝飾視圖可以看作另一種類型的追加視圖,和追加視圖一樣踊赠,它們的使用和布局也是由布局對象決定的律姨。
Collection view在多個不同的時刻都會從它的布局對象獲取這些元素的布局信。每一個呈現(xiàn)在屏幕上的cell和view臼疫,布局信息都來自布局對象。同樣扣孟,在collection view插入或者刪除item的時候,就會有相應(yīng)的布局對象被加入或者刪除烫堤。需要注意的是,collection view進(jìn)行的布局限制在屏幕可見的范圍之內(nèi)凤价。(注:即鸽斟,即便創(chuàng)建了所有元素的布局對象,但是真正的布局只是在可見的范圍內(nèi)利诺,超出屏幕的部分沒有布局)
需要重寫的方法 :
每一個布局對象需要實現(xiàn)接下來的幾個方法
- (CGSize)collectionViewContentSize;
返回collectionView內(nèi)容區(qū)的寬度和高度富蓄,子類必須重載該方法,返回值代表了所有內(nèi)容的寬度和高度慢逾,而不僅僅是可見范圍的立倍,collectionView通過該信息配置它的滾動范圍,默認(rèn)返回 CGSizeZero侣滩。
- (NSArray<__kindofUICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
返回UICollectionViewLayoutAttributes 類型的數(shù)組口注,UICollectionViewLayoutAttributes 對象包含cell或view的布局信息。子類必須重載該方法君珠,并返回該區(qū)域內(nèi)所有元素的布局信息寝志,包括cell,追加視圖和裝飾視圖。
在創(chuàng)建 layout attributes的時候策添,創(chuàng)建的是相應(yīng)元素類型(cell, supplementary, decoration)的 attributes對象,比如:
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;
collection view 根據(jù)不同的類型區(qū)分屬性材部,并根據(jù)這些信息決定創(chuàng)建怎樣的視圖及如何進(jìn)行管理。
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
返回指定indexPath的item的布局信息唯竹。子類必須重載該方法,該方法只能為cell提供布局信息乐导,不能為補充視圖和裝飾視圖提供。
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
如果你的布局支持追加視圖的話摩窃,必須重載該方法兽叮,該方法返回的是追加視圖的布局信息芬骄,kind這個參數(shù)區(qū)分段頭還是段尾的,在collectionview注冊的時候回用到該參數(shù)鹦聪。
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;
如果你的布局支持裝飾視圖的話账阻,必須重載該方法,該方法返回的是裝飾視圖的布局信息泽本,ecorationViewKind這個參數(shù)在collectionview注冊的時候回用到
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
該方法用來決定是否需要更新布局淘太。如果collection view需要重新布局返回YES,否則返回NO,默認(rèn)返回值為NO。子類重載該方法的時候规丽,基于是否collection view的bounds的改變會引發(fā)cell和view布局的改變蒲牧,給出正確的返回值。
如果collection view的bounds改變,該方法返回YES赌莺,collection view通過調(diào)用
invalidateLayoutWithContext方法使原來的layout失效
這些方法為collection view 在屏幕上布局提供了最基礎(chǔ)的布局信息冰抢,如果你不想為追加視圖和裝飾視圖布局,可以不去重載相應(yīng)的方法艘狭。
當(dāng)collection view的數(shù)據(jù)發(fā)生改變的時候挎扰,比如插入或者刪除 item的時候,collection view將會要求布局對象更新相應(yīng)的布局信息巢音。移動遵倦、添加、刪除 items時都必須更新相應(yīng)的布局信息以便反映元素最新的位置官撼。對于移動的元素梧躺, collection view提供了標(biāo)準(zhǔn)的方法獲取更新后的布局信息。而collection view刪除或者添加元素的時候傲绣,將會調(diào)用一些不同的方法掠哥,你應(yīng)該重載以便提供正確的布局信息:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
在一個 item被插入到collection view 的時候,返回開始的布局信息斜筐。這個方法在 prepareForCollectionViewUpdates:之后和finalizeCollectionViewUpdates 之前調(diào)用龙致。collection view將會使用該布局信息作為動畫的起點(結(jié)束點是該item在collection view 的最新的位置)。如果返回為nil顷链,布局對象將用item的最終的attributes 作為動畫的起點和終點目代。
//返回值是追加視圖插入collection view時的布局信息。該方法使用同initialLayoutAttributesForAppearingItemAtIndexPath:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath
//返回值是裝飾視圖插入collection view時的布局信息嗤练。該方法使用同initialLayoutAttributesForAppearingItemAtIndexPath:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
返回值是item即將從collection view移除時候的布局信息榛了,對即將刪除的item來講,該方法在 prepareForCollectionViewUpdates: 之后和finalizeCollectionViewUpdates 之前調(diào)用煞抬。在該方法中返回的布局信息描包含 item的狀態(tài)信息和位置信息霜大。 collection view將會把該信息作為動畫的終點(起點是item當(dāng)前的位置)。如果返回為nil的話革答,布局對象將會把當(dāng)前的attribute战坤,作為動畫的起點和終點曙强。
//....不用解釋了吧 .....
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath
//....不用解釋了吧 .....
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath
除了這些方法之外,你也可以重載- (void)prepareForCollectionViewUpdates:(NSArray<UICollectionViewUpdateItem *> *)updateItems;
做一些和布局相關(guān)的準(zhǔn)備工作途茫。也可以重載- (void)finalizeCollectionViewUpdates;
通過該方法添加一些動畫到block碟嘴,或者做一些和最終布局相關(guān)的工作。
通過使上下文無效( Invalidation Contexts )來優(yōu)化布局:
在自定義布局的時候囊卜,應(yīng)該通過使那些布局真正發(fā)生改變的部分無效來提升布局性能娜扇。在你改變item的時候,通過調(diào)用invalidateLayout方法強制collection view重新計算全部的布局信息栅组,并應(yīng)用該布局信息雀瓢。一個更好的方式是只重新計算發(fā)生改變的布局信息,這恰恰是Invalidation Contexts允許你去做的玉掸。你可以通過Invalidation Context指明布局發(fā)生改變的部分刃麸,布局對象就可以根據(jù)該信息 減小重新計算的數(shù)據(jù)量。
通過子類化 UICollectionViewLayoutInvalidationContext
,為你的布局定義一個 自定義的 invalidation context司浪。在子類中定義一些屬性嫌蚤,這些屬性代表布局中可以單獨重新計算的數(shù)據(jù)。當(dāng)你需要 invalidate 你的布局時断傲,創(chuàng)建一個 invalidation context 子類的實例,配置自定義的屬性智政,并把該實例傳給invalidateLayoutWithContext: 方法认罩。你自定義的方法可以根據(jù)invalidation context 中的信息重新計算布局改變的部分。
如果你定義了一個自定義的 invalidation context 類续捂,你也應(yīng)該重載invalidationContextClass方法垦垂,返回自定義的類。 collection view 在需要invalidation context時牙瓢,總是會創(chuàng)建一個指明的類實例劫拗。返回你自定義的子類,確保了自定義的對象擁有正確的 invalidation context矾克。
獲取 Collection View 的信息:
//當(dāng)一個新的布局對象分配給collection view時页慷,collection view將設(shè)置該屬性的值
@property(nonatomic, readonly) UICollectionView *collectionView
//上面已經(jīng)介紹過,不再介紹
- collectionViewContentSize胁附;
+ (Class)layoutAttributesClass
返回創(chuàng)建布局信息時用到的類酒繁,如果你創(chuàng)建了繼承自 UICollectionViewLayoutAttributes的子類,你也應(yīng)該重載該方法控妻,返回該子類州袒。該方法主要是為了子類化,無需在代碼中調(diào)用弓候。
- (void)prepareLayout
該方法將通知布局對象更新當(dāng)前的布局郎哭。布局更新發(fā)生在 collection view 第一次展示它的內(nèi)容的時候他匪,以及由于view的改變導(dǎo)致布局 invalidated 的時候。在布局更新期間夸研, collection view都會首先調(diào)用該方法邦蜜,允許布局對象對此次的更新做一些準(zhǔn)備操作。
默認(rèn)情況下陈惰,該方法不會做任何操作畦徘。子類可以重載該方法,在方法內(nèi)部做一些和布局相關(guān)的數(shù)據(jù)創(chuàng)建或計算操作抬闯。
- (UICollectionViewLayoutAttributes *)layoutAttributesForInteractivelyMovingItemAtIndexPath:(NSIndexPath *)indexPath withTargetPosition:(CGPoint)position
當(dāng)item在手勢交互下移動時井辆,通過該方法返回這個item布局的attributes 。默認(rèn)實現(xiàn)是溶握,復(fù)制已存在的attributes杯缺,改變attributes兩個值,一個是中心點center睡榆;另一個是z軸的坐標(biāo)值萍肆,設(shè)置成最大值。所以該item在collection view的最上層胀屿。子類重載該方法塘揣,可以按照自己的需求更改attributes,首先需要調(diào)用super類獲取attributes,然后自定義返回的數(shù)據(jù)結(jié)構(gòu)宿崭。
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
在進(jìn)行動畫式布局的時候亲铡,該方法返回內(nèi)容區(qū)的偏移量。在布局更新或者布局轉(zhuǎn)場的時候葡兑,collection view 調(diào)用該方法改變內(nèi)容區(qū)的偏移量奖蔓,該偏移量作為動畫的結(jié)束點。如果動畫或者轉(zhuǎn)場造成item位置的改變并不是以最優(yōu)的方式進(jìn)行讹堤,可以重載該方法進(jìn)行優(yōu)化吆鹤。 collection view在調(diào)用prepareLayout 和 collectionViewContentSize 之后調(diào)用該方法
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
該方法返回值為滑動停止的點。如果你希望內(nèi)容區(qū)快速滑動到指定的區(qū)域洲守,可以重載該方法疑务。比如,你可以通過該方法讓滑動停止在兩個item中間的區(qū)域梗醇,而不是某個item的中間暑始。
和collection view更新相關(guān)的方法:
- (void)prepareForCollectionViewUpdates:(NSArray<UICollectionViewUpdateItem *> *)updateItems
當(dāng)插入或者刪除 item的時候,collection view將會通知布局對象它將調(diào)整布局婴削,第一步就是調(diào)用該方法告知布局對象發(fā)生了什么改變廊镜。除此之外,該方法也可以用來獲取插入唉俗、刪除嗤朴、移動item的布局信息配椭。
//該方法上文已介紹
- (void)finalizeCollectionViewUpdates
- (NSArray<NSIndexPath *> *)indexPathsToInsertForSupplementaryViewOfKind:(NSString *)kind
返回一個NSIndexPath 類型的數(shù)組,該數(shù)組存放的是將要添加到collection view中的追加視圖的 NSIndexPath 雹姊。往collection view添加cell或者section的時候股缸,就會調(diào)用該方法。collection view將會在prepareForCollectionViewUpdates: 和finalizeCollectionViewUpdates之間調(diào)用該方法吱雏。
- (NSArray<NSIndexPath *> *)indexPathsToInsertForDecorationViewOfKind:(NSString *)kind
//類比上個方法敦姻,不再過多解釋;
- (NSArray<NSIndexPath *> *)indexPathsToDeleteForSupplementaryViewOfKind:(NSString *)kind
- (NSArray<NSIndexPath *> *)indexPathsToDeleteForDecorationViewOfKind:(NSString *)kind;
返回一個NSIndexPath 類型的數(shù)組歧杏,該數(shù)組存放的是將要從collection view中刪除的追加視圖的 NSIndexPath 镰惦。從collection view刪除cell或者section的時候,就會調(diào)用該方法犬绒。collection view將會在prepareForCollectionViewUpdates: 和finalizeCollectionViewUpdates之間調(diào)用該方法旺入。
類比)indexPathsToDeleteForDecorationViewOfKind:
- (NSIndexPath *)targetIndexPathForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath withPosition:(CGPoint)position
根據(jù)item在collection view中的位置獲取該item的index path。第一個參數(shù)該item原來的index path凯力,第二個參數(shù)是item在collection view中的位置茵瘾。在item移動的過程中,該方法將collection view中的location映射成相應(yīng) index paths咐鹤。該方法的默認(rèn)是現(xiàn)實拗秘,查找指定位置的已經(jīng)存在的cell,返回該cell的 index path 祈惶。如果在相同的位置有多個cell聘殖,該方法默認(rèn)返回最上層的cell。
你可以通過重載該方法來改變 index path的決定方式行瑞。比如,你可以返回z坐標(biāo)軸最底層cell的index path.當(dāng)你重載該方法的時候餐禁,沒有必要去調(diào)用super類該方法血久。
使布局失效:
- (void)invalidateLayout
使當(dāng)前的布局失效,同時觸發(fā)布局更新帮非,可以在任何時間調(diào)用該方法更新布局信息氧吐。該方法使collection view的布局立即失效,你可以在不觸發(fā)多次更新的情況下末盔,在同一個block中多次調(diào)用該方法筑舅。重載該方法的時候,必須調(diào)用super類的該方法陨舱。
- (void)invalidateLayoutWithContext:(UICollectionViewLayoutInvalidationContext *)context
該方法使布局的部分區(qū)域失效翠拣,而不是全局失效。是對invalidateLayout的一種優(yōu)化游盲,這種優(yōu)化基于UICollectionViewLayoutInvalidationContext 類型的屬性误墓。如果你自定義了UICollectionViewLayoutInvalidationContext 蛮粮,就應(yīng)該重載該方法,并使用自定義的類型谜慌。重載該方法的時候然想,必須調(diào)用super類。
+ (Class)invalidationContextClass;
當(dāng)你子類化UICollectionViewLayout欣范,并且使用自定義的 invalidation context對象優(yōu)化布局更新变泄,重載該方法,返回UICollectionViewLayoutInvalidationContext的子類恼琼。當(dāng) collection view 需要invalidate你的布局的時候妨蛹,將會用該方法返回值創(chuàng)建正確的invalidation context 對象。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
該方法決定了collection view是否能夠進(jìn)行布局更新驳癌,默認(rèn)為NO滑燃。子類在重載該方法的時候,根據(jù)cell或者追加視圖是否發(fā)生布局改變颓鲜,返回正確的值表窘。
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newbounds;
collection view 的bounds發(fā)生改變的時候返回的無效上下文
,該無效上下文
描述了bounds變化后需要做出改變的部分甜滨。
該方法默認(rèn)實現(xiàn)是乐严,通過invalidationContextClass方法返回的類創(chuàng)建一個實例,并作為返回值衣摩。如果你想獲得一個自定義的無效上下文
對象昂验,就要重載invalidationContextClass方法。
你可以通過重載該方法去創(chuàng)建和配置自定義的無效上下文
艾扮。如果你重載該方法既琴,第一步應(yīng)該調(diào)用super類獲取無效上下文
,在獲得該無效上下文
后,為它設(shè)置自定義的屬性泡嘴,并返回甫恩。
- (BOOL)shouldInvalidateLayoutForPreferredLayoutAttributes:(UICollectionViewLayoutAttributes *)preferredAttributes withOriginalAttributes:(UICollectionViewLayoutAttributes *)originalAttributes
當(dāng)collection view包含self-sizing (自排列)的cell時,這些cell可以在布局attributes 應(yīng)用到它之前更改這些attributes酌予。一個自排列的cell指明一個不同于布局對象給出的size的時候磺箕,就會這么做。當(dāng)cell設(shè)置一系列不同的attributes時抛虫,collection view將會調(diào)用該方法判斷是否需要更新布局松靡,默認(rèn)返回為NO。
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForPreferredLayoutAttributes:(UICollectionViewLayoutAttributes *)preferredAttributes withOriginalAttributes:(UICollectionViewLayoutAttributes *)originalAttributes
該方法返回值是一個上下文建椰,上下文包含布局中需要改變的信息雕欺。默認(rèn)的實現(xiàn)是,使用invalidationContextClass 方法返回的類創(chuàng)建一個實例,并返回阅茶。
你可以通過重載該方法去創(chuàng)建和配置自定義的無效上下文
蛛枚。如果你重載該方法,第一步應(yīng)該調(diào)用super類獲取無效上下文
,在獲得該無效上下文
后脸哀,為它設(shè)置自定義的屬性蹦浦,并返回。
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition;
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled;
調(diào)整動態(tài)的變化:
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds撞蜂;
collection view在bounds動態(tài)改變或者插入盲镶、刪除items之前,調(diào)用該方法蝌诡「然撸可以在該方法進(jìn)行一些相關(guān)的計算,比如可以在該方法內(nèi)部計算插入和刪除的item初始和最終的位置浦旱。你也可用通過該方法添加動畫宇色,這些動畫被用于處理item插入、刪除和bounds改變颁湖。
- (void)finalizeAnimatedBoundsChange;
該方法在item插入宣蠕、刪除和bounds改變動畫完成之后,清空相關(guān)的操作甥捺。
在布局之間轉(zhuǎn)換:
- (void)prepareForTransitionFromLayout:(UICollectionViewLayout *)oldLayout;
告知布局對象將會作為新的布局被導(dǎo)入到 collection view抢蚀,該方法先于轉(zhuǎn)場之前執(zhí)行,可以在該方法做一些初始化的操作镰禾,生成布局attributes;
- (void)prepareForTransitionToLayout:(UICollectionViewLayout *)newLayout;
告知布局對象作為布局即將從collection view移除皿曲,該方法先于轉(zhuǎn)場之前執(zhí)行,可以在該方法做一些初始化的操作吴侦,生成布局attributes;
- (void)finalizeLayoutTransition屋休;
collection view在獲取從一個布局向另一個布局轉(zhuǎn)場的時候所有的布局attributes 后,調(diào)用該方法备韧。你可以用該方法清空prepareForTransitionFromLayout: 和prepareForTransitionToLayout:生成的數(shù)據(jù)和緩存劫樟。
注冊裝飾視圖:
- (void)registerClass:(Class)viewClass forDecorationViewOfKind:(NSString *)decorationViewKind;
- (void)registerNib:(UINib *)nib forDecorationViewOfKind:(NSString *)decorationViewKind;
demo
效果圖:
效果圖:
結(jié)語:
本文并未對UICollectionView多做介紹,只是對官方UICollectionViewLayout文檔進(jìn)行翻譯盯蝴,筆者沒有逐字逐句的進(jìn)行翻譯,在翻譯的過程中也加入了自己的理解听怕。如果讀者覺得有什么不妥的地方捧挺,請予以指正,筆者會加以改正并及時更新尿瞭。
參考:
http://my.oschina.net/u/2340880
http://yulingtianxia.com/blog/2014/08/17/New-in-Table-and-Collection-Views/?utm_source=tuicool&utm_medium=referral