滾動菜單是app展示數(shù)據(jù)常用的控件,使用頻率極高钻心,實(shí)現(xiàn)的過程就是對UIScrollView的應(yīng)用旦委,大體都是頂部一個標(biāo)題滾動視圖,下邊一個用來鋪顯示數(shù)據(jù)的View的滾動視圖朴则,然后實(shí)現(xiàn)聯(lián)動效果权纤,基本差不多了。
需求分析
- 從筆者安裝的應(yīng)用上觀察乌妒,大體要實(shí)現(xiàn)功能標(biāo)題下劃線自適應(yīng)汹想,菜單的半透明,左右的不同按鈕(參考優(yōu)酷撤蚊,網(wǎng)易云音樂)古掏。
屏幕快照 2016-10-11 10.31.31.png
屏幕快照 2016-10-11 10.26.41.png
屏幕快照 2016-10-11 10.26.53.png
實(shí)現(xiàn)
-
總思想
將標(biāo)題視圖,展示數(shù)據(jù)的視圖放到一個View 上,View 來管理不同的子視圖的滑動侦啸,子視圖由各自的控制管理槽唾,要做的就是封裝好這個視圖,名字就叫HYPageView光涂。
屏幕快照 2016-10-11 10.53.26.png
簡單的說就是把控制器的View放到scrollView,滑到某一頁庞萍,就加載某一頁的控制器,并且將控制器的View放到這一頁忘闻。需要注意的是钝计,創(chuàng)建的控制器必需要強(qiáng)引用,因?yàn)榫植孔兞繒?dǎo)致"控制器死掉"造成控制器里邊所有事件無法響應(yīng)齐佳,比如tabView沒有Cell私恬,Button點(diǎn)上去沒反應(yīng)等等,都是因?yàn)榭刂破鲝膬?nèi)存釋放了炼吴。
- 下劃線自適應(yīng)
HYPageView00.gif
就是一邊滑本鸣,一邊動,根據(jù)當(dāng)前滑動的位置改變線條的位置硅蹦,線條的長度也總是趨于下一個位置的長度荣德,這種很細(xì)節(jié)的東西吸引了筆者闷煤,如何實(shí)現(xiàn)呢?
需要計算兩點(diǎn)位置命爬,長度曹傀;
- 方法1(坑)
首先想到是 計算相對偏移量,我們總是能知道當(dāng)前位置和下一個位置饲宛,只要計算線條位置相對與手指拖拽的位置的偏移量,通過加減就可以算出準(zhǔn)確的位置嗜价,線條的長度一個道理艇抠。感覺有點(diǎn)麻煩,但我嘗試的這么做了久锥。
在UIScrollViewDelegate 里的scrollViewDidScroll 方法中算出并記錄每一次偏移量家淤,然后累計到下劃線上,結(jié)果是效果差不多瑟由。但總是有誤差絮重,誤差的原因是誤差累計,與越界歹苦,前者是在計算的過程精度的丟失青伤,多次累加產(chǎn)生的誤差,后者是在滑動的過程殴瘦,結(jié)束的位置不會保證是下一個位置的結(jié)束點(diǎn)狠角,我也嘗試的修復(fù)這種誤差,但滑的越快誤差就越大蚪腋。然后我就嘗試尋找更好的方法丰歌。
找到了一個例子,和方法1的坑一毛一樣
傳送門??快速集成App中頂部標(biāo)題滾動條
反例1.gif
越來長的原因:可能是越界的問題屉凯,從圖中可以看出立帖,在返回自身位置時是一個累減的過程,而返回的最后一段距離終點(diǎn)不一定恰好是零界點(diǎn)悠砚,這段長度可能沒有減去晓勇,拖著不放,誤差越來越大哩簿。從圖中也可以看出作者在結(jié)束滾動時對線條進(jìn)行了校正宵蕉。
- 方法2
想要精確無誤,就想到了利用數(shù)學(xué)的知識來解決节榜,通過觀察想到了contentOffset 的值與線條點(diǎn)的位置唯一對應(yīng)羡玛,也就是說線條點(diǎn)的位置是關(guān)于contentOffset的一個函數(shù),不難發(fā)現(xiàn)就是一個分段函數(shù)宗苍,而且每一段都是一個 一次函數(shù) 形如y=kx+b 這里x 是 contentOffset 稼稿,y 是 線條的點(diǎn)或長度薄榛,寫一個方法,傳入contentOffset返回點(diǎn)的位置或線條的寬度让歼,顯然要一組k與b敞恋,而k 與 b 在 構(gòu)造標(biāo)題位置的就可以計算出來 k = △y/△x ,得到k后b也可以得到谋右。
然后在- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
方法里只有兩行代碼
_lineBottom.center = CGPointMake([self getTitleWidth:scrollView.contentOffset.x], _lineBottom.center.y);
_lineBottom.bounds = CGRectMake(0, 0, [self getTitlePoint:scrollView.contentOffset.x], LINEBOTTOM_HEIGHT);
計算的代碼
#pragma mark - Calculation Method
- (CGFloat)getTitleWidth:(CGFloat)offset{
NSInteger index = (NSInteger)(offset / _selfFrame.size.width);
CGFloat k = [_width_k_array[index] floatValue];
CGFloat b = [_width_b_array[index] floatValue];
CGFloat x = offset;
return k * x + b;
}
- (CGFloat)getTitlePoint:(CGFloat)offset{
NSInteger index = (NSInteger)(offset / _selfFrame.size.width);
CGFloat k = [_point_k_array[index] floatValue];
CGFloat b = [_point_b_array[index] floatValue];
CGFloat x = offset;
return k * x + b;
}
這樣就達(dá)到完美的線條自適應(yīng)效果如下:
HYPageView03.gif
HYPageView08.gif
-
菜單的半透明
菜單的半透明很簡單硬猫,設(shè)置好ScrollView的contentInset和frame就好了,需要注意的是有navigationController時控制器的automaticallyAdjustsScrollViewInsets 屬性 和 edgesForExtendedLayout屬性在開啟navigationBar 半透明效果時 navigationController會對 控制器的 contentInset 和 frame進(jìn)行調(diào)整 以達(dá)到半透明效果改执。 -
左右的不同按鈕
前邊的步驟做好了這里也很簡單啸蜜,設(shè)計可以根據(jù)navigationBar 的左右item來設(shè)計。筆者直接添加Button然后重新計算頂部菜單滾動視圖的寬辈挂,效果如下:
HYPageView05.gif
HYPageView06.gif
- 接口的設(shè)計
/**
Initializes and returns a newly allocated view object with the specified frame rectangle.
@param frame ...
@param titles Some title
@param controllers Name of some controllers
@param parameters You need to set a property called "parameter" for your controller to receive.
@return self
*/
- (instancetype)initWithFrame:(CGRect)frame withTitles:(NSArray *)titles withViewControllers:(NSArray *)controllers withParameters:(NSArray *)parameters;
```
frame衬横,titles 不用說,controllers是傳控制器的名稱终蒂,這樣可以在需要的時候再創(chuàng)建控制器蜂林,parameters是要給控制器傳的參數(shù),傳遞的方式是KVC,當(dāng)然傳遞的時候需要檢查參數(shù)是否存在拇泣,控制器是否有parameters參數(shù)噪叙,具體實(shí)現(xiàn)可以看demo。
https://github.com/runlhy/HYPageView