之前在看iCarousel源碼的時候發(fā)現(xiàn)其中的效果都是可以用UICollectionView來實現(xiàn)的,作為UITableView的替代框架,UICollectionView更為先進,正好最近有一個分類條目在上面,可以滑動和點擊分類切換頁面的需求。在iPhone上面關(guān)于左右滑動的切換頁面的控件已經(jīng)很多了,像類似于今日頭條、網(wǎng)易新聞等咨詢類app的首頁都是使用這種架構(gòu),京東app的好東西也是這種架構(gòu),不過是多了一個選中的放大效果。沒有時間看開源大神的想法,這里就自己用UICollectionView實現(xiàn)了一個。(Demo沒有做例子,如果想要直接使用的話請留言,我會加上)

LIXScrollTopBarView
首先主要提供了基礎(chǔ)的兩種切換頁面的效果,提供了多種頂部titleBar的多種選中效果。包括顏色提供漸變和突變兩種樣式,大小提供了根據(jù)顏色條靠近的距離時間大小的漸變,可以定義底部選中條的顏色,可以定義每個分類下選中條的大小。最近看到優(yōu)酷、微博等app在選中條上面做文章,感覺也是挺有心意,以后會考慮加上。底部的內(nèi)容視圖的話可以編輯任意一個cell,總之下面的內(nèi)容視圖就是一個UICollectionView,可以實現(xiàn)各種自定義的layout。
使用方法
首先是titleBar的幾種樣式可供選擇,在使用的時候可以直接設(shè)置scrollTitleType屬性就可以了,下面提供幾種style的默認(rèn)樣式。當(dāng)然你也可以傳入自己的flowLayout。
-
LIXScrollTopBarType_default
展示圖片 -
LIXScrollTopBarType_transform & LIXScrollTopBarType_default
defaultcolor&transform -
LIXScrollTopBarType_transform & LIXScrollTopBarType_gradient
colorGradient
各種樣式可以疊加使用,也可以單獨使用,疊加的時候可以出現(xiàn)各種效果,就不一一展示了。
關(guān)于內(nèi)容視圖的滾動也提供了一種加速度的樣式,最近看到百度外賣的app有加入這種模式。但是在實踐的過程中發(fā)現(xiàn),這種方式在滑動過程中進行屏幕旋轉(zhuǎn)的過程中會出現(xiàn)crash。如不不支持橫豎屏適配的話是完全沒問題的。
-
contentCellScrollStyle = LIXScrollTopBarContentScrollStyle_dynamic
加速度
加速度效果使用的是dynamic框架,是蘋果提供用來模仿物理效果的。其原理也是計算每個cell距離觸點的位置,然后改變每個cell的frame,在frame改變的過程中加入物理加速度的效果。
結(jié)構(gòu)

如圖所示,使用的類比較多,因為是在UICollectionView的基礎(chǔ)上進行的封裝,所以遵守的是UICollectionView的使用方法。
- LIXScrollTopBarView是主要的類,主要是管理上下兩個UICollectionView的同步問題,主要是要根據(jù)下面UICollectionView的移動距離,來同步上面UICollectionView的選中態(tài)。
- LIXScrollTopBarTitleBarView可能根據(jù)名字很難看出來是干什么的,這個是下面的指示條,如果你困惑為什么一個指示條還要單獨建一個類的話,其實就是萬惡的設(shè)計想要每個title分類下都展示和文字同樣長度的指示條(為什么說是萬惡的設(shè)計那,因為他最后又給去掉了,讓展示同樣長度的指示條)
- LIXScrollTopBarTitleCell是分類的cell,LIXScrollTopBarCell是下面UICollectionView的Cell,里面的又是一個UICollectionView,最里面的cell是LIXScrollTopBarContentCell。
- flowLayout,因為是簡單的流式布局,所以直接使用的是flowLayout,對于flowLayout的話,就是每個UICollectionView就對應(yīng)一個flowLayout
- model分類下的是每個頁面的model(LIXScrollTopBarViewDataSourceModel)和整個頁面的數(shù)據(jù)(LIXScrollTopBarViewDataSource),整個頁面的數(shù)據(jù),也可以說成是manager吧。
- LIXScrollTopBarTitleCellLayoutAttributes這個是定制的UICollectionViewCellAttributes,因為有部分屬性需要改變,但是attributes類并沒有提供,這里繼承擴展一些。
后期優(yōu)化使用工廠方法來創(chuàng)建對象,還有就是類的命名問題,真的成了開發(fā)效率的瓶頸了,這個地方需要加強一下。
核心思想
核心思想是最大的可定制化吧,剛開始的時候想的是可以使用viewController之間的自定義轉(zhuǎn)場也可以實現(xiàn)這種效果,而且可以完美的管理每個view的生命周期;或者是直接使用ScrollView,計算每個page頁面所在的位置,像iCarousel那樣建立自己的重用隊列,管理重用問題;當(dāng)然也可以像iCarousel那樣,不使用scrollView,直接計算每個page頁面的transform,自己管理重用。后來考慮到UICollectionView有現(xiàn)成的API可以調(diào)用,而且對于flowLayout分離的方式可以最大程度的提供靈活的切換方式(完全可以新建一個flowLayout,創(chuàng)建不拘于實現(xiàn)過的幾種方式)。
UICollectionView基礎(chǔ)
并沒有使用很復(fù)雜的UICollectionView的知識吧,主要的就是flowLayout和每個cell的交互花了點心思,這里還是需要一些UICollectionView的基礎(chǔ)知識的。下面列出來幾點用到的。
自定義UICollectionViewCellAttributes
attributes類提供的功能主要是定制cell的frame,transFrom等屬性,一個attributes對應(yīng)一個cell,管理一個cell的layout。但是有些沒有進行默認(rèn)的提供,比如我們想改變字體的顏色,我們就可以定制其子類,加上一個fontColor屬性,當(dāng)滑動到相應(yīng)的cell的時候就可以發(fā)生刷新cell的樣式。這是每個title cell可以定制化的基礎(chǔ)。
UICollectionViewFlowLayout與Attributes的交互
既然每個cell都對應(yīng)一個attributes屬性的話,那么layout做的事情就是管理這些attributes的變化。UICollectionViewFlowLayout是UICollectionViewLayout的定制子類,是一種特定的流式布局,用起來比直接使用layout簡單一下,定制起來也有些不同。這里只說到用到的flowLayout。在我的理解看來,layout類的功能就是刷新每個attributes。
+ (Class)layoutAttributesClass {
return [JDScrollTopBarTitleCellLayoutAttributes class];
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in layoutAttributesArray) {
//如果判斷交集的話會出現(xiàn)復(fù)用的時候?qū)傩圆粚Φ膯栴}
// if(CGRectIntersectsRect(attributes.frame, rect)) {
[self applyAttributes:attributes forVisibleRect:rect];
// }
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, CGRectGetWidth(self.collectionView.bounds), CGRectGetHeight(self.collectionView.bounds));
[self applyAttributes:layoutAttributes forVisibleRect:visibleRect];
return layoutAttributes;
}
一個是返回attributes數(shù)組,一個是返回每個indexPath對應(yīng)下的attributes。layoutAttributesClass方法是將默認(rèn)的attributes類替換成我們的attributes類,
shouldInvalidateLayoutForBoundsChange是bounds發(fā)生改變的時候更新attributes,這里boundsChange也可以理解成UIScrollVIew滾動,因為UIScrollView的實現(xiàn)方式就是改變view的bounds,所以在滾動的時候就會更新attributes了。
UICollectionViewCell接收Attributes屬性
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
[super applyLayoutAttributes:layoutAttributes];
self.titleLabel.textColor = [(JDScrollTopBarTitleCellLayoutAttributes *)layoutAttributes textColor];
//使用shouldRasterize會導(dǎo)致label文字模糊
// self.layer.shouldRasterize = [layoutAttributes valueForKey:@"shouldRasterize"];
CGFloat fontSize = 16 * [(JDScrollTopBarTitleCellLayoutAttributes *)layoutAttributes fontScale];
self.titleLabel.font = [UIFont systemFontOfSize:fontSize];
self.layer.affineTransform = layoutAttributes.transform;
}
在這個方法里面,attributes發(fā)生更新的時候,cell就能過得到通知,應(yīng)該是UICollectionView在底層做了一個觀察者吧。layer有一個shouldRasterize屬性,該方法可以柵格化layer,并且觸發(fā)離屏渲染,緩存柵格化之后的數(shù)據(jù),這樣在cell比較多的情況下會有很好的內(nèi)存表現(xiàn)。但是這里會引發(fā)一個問題,就是label問題模糊,不知道是否是渲染文字的時候出現(xiàn)的問題,有大神知道的話望留言告知。
具體實現(xiàn)
基于UICollectionView的以上各種,完成了ScrollTopBar。通過TopBarCell和titleCell之間的比例,可以計算出滑動之后標(biāo)題指示條的位置。根據(jù)指示條中心的位置和titleCell的位置之間的差值來計算選中的titleCell。具體實現(xiàn)可以參照源碼(github地址:https://github.com/lixuzong/LIXScrollTopBarDemo)。具體講一下開發(fā)中遇到的問題。
問題及解決
- 最棘手的問題是,在滑動底部的UICollectionView的時候,沒辦法讓titleBar的cell的attributes發(fā)生更新操作。這里的解決方法是在didScroll方法里面計算出相應(yīng)的cell,并且主動調(diào)用cell的applyAttributes方法。
- 需要定制選中指示條,但是指示條是計算選中cell的基礎(chǔ),不能隨意變動,所以設(shè)計成一個view樹,最外層的view是透明的,里面的子view提供顏色,這樣就可以根據(jù)需求給指示條做動畫,也可以根據(jù)每個不同的cell做不同的變化等。
- 關(guān)于數(shù)據(jù)的同步,感覺處理的不夠好,但是為了偷懶,是用一個單例來實現(xiàn)了,在網(wǎng)上也找到替換單例的方法,就是注入依賴替換單例,說白了就是在創(chuàng)建的時候?qū)?shù)據(jù)傳入,跟JS里面?zhèn)鬟fprop有點類似吧。


