一款滾動菜單的實現

lxktest123 8年前發布 | 7K 次閱讀 iOS開發 移動開發

滾動菜單是app展示數據常用的控件,使用頻率極高,實現的過程就是對UIScrollView的應用,大體都是頂部一個標題滾動視圖,下邊一個用來鋪顯示數據的View的滾動視圖,然后實現聯動效果,基本差不多了。

需求分析

  • 從筆者安裝的應用上觀察,大體要實現功能 標題下劃線自適應菜單的半透明左右的不同按鈕 (參考優酷,網易云音樂)。

屏幕快照 2016-10-11 10.31.31.png

屏幕快照 2016-10-11 10.26.41.png

屏幕快照 2016-10-11 10.26.53.png

實現

  • 總思想

    將標題視圖,展示數據的視圖放到一個View 上,View 來管理不同的子視圖的滑動,子視圖由各自的控制管理,要做的就是封裝好這個視圖,名字就叫HYPageView。

屏幕快照 2016-10-11 10.53.26.png

簡單的說就是把控制器的View放到scrollView,滑到某一頁,就加載某一頁的控制器,并且將控制器的View放到這一頁。需要注意的是, 創建的控制器必需要強引用,因為局部變量會導致"控制器死掉" 造成控制器里邊所有事件無法響應,比如tabView沒有Cell,Button點上去沒反應等等,都是因為控制器從內存釋放了。

  • 下劃線自適應

HYPageView00.gif

就是一邊滑,一邊動,根據當前滑動的位置改變線條的位置,線條的長度也總是趨于下一個位置的長度,這種很細節的東西吸引了筆者,如何實現呢?

需要計算兩點 位置長度

  • 方法1(坑)

    首先想到是 計算相對偏移量 ,我們總是能知道當前位置和下一個位置,只要計算線條位置相對與手指拖拽的位置的偏移量,通過加減就可以算出準確的位置,線條的長度一個道理。感覺有點麻煩,但我嘗試的這么做了。

    在UIScrollViewDelegate 里的scrollViewDidScroll 方法中算出并記錄每一次偏移量,然后累計到下劃線上,結果是效果差不多。但總是有誤差,誤差的原因是誤差累計,與越界,前者是在計算的過程精度的丟失,多次累加產生的誤差,后者是在滑動的過程,結束的位置不會保證是下一個位置的結束點,我也嘗試的修復這種誤差,但滑的越快誤差就越大。然后我就嘗試尋找更好的方法。

找到了一個例子,和方法1的坑一毛一樣

反例1.gif

越來長的原因:可能是越界的問題,從圖中可以看出,在返回自身位置時是一個累減的過程,而返回的最后一段距離終點不一定恰好是零界點,這段長度可能沒有減去,拖著不放,誤差越來越大。從圖中也可以看出作者在結束滾動時對線條進行了校正。

  • 方法2

    想要精確無誤,就想到了利用 數學的知識 來解決,通過觀察想到了contentOffset的值與線條點的位置唯一對應 ,也就是說 線條點的位置是關于contentOffset的一個函數,不難發現就是一個分段函數 ,而且每一段都是一個 一次函數 形如 y=kx+b 這里x是 contentOffsety 是 線條的點或長度 ,寫一個方法,傳入contentOffset返回點的位置或線條的寬度,顯然要一組k與b,而k 與 b 在 構造標題位置的就可以計算出來 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;
}

這樣就達到完美的線條自適應效果如下:

HYPageView03.gif

HYPageView08.gif

  • 菜單的半透明
    菜單的半透明很簡單,設置好ScrollView的contentInset和frame就好了,需要注意的是有navigationController時控制器的automaticallyAdjustsScrollViewInsets 屬性 和 edgesForExtendedLayout屬性在開啟navigationBar 半透明效果時 navigationController會對 控制器的 contentInset 和 frame進行調整 以達到半透明效果。

  • 左右的不同按鈕
    前邊的步驟做好了這里也很簡單,設計可以根據navigationBar 的左右item來設計。筆者直接添加Button然后重新計算頂部菜單滾動視圖的寬,效果如下:

HYPageView05.gif

HYPageView06.gif

  • 接口的設計

/**
 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是傳控制器的名稱,這樣可以在需要的時候再創建控制器,parameters是要給控制器傳的參數,傳遞的方式是KVC,當然傳遞的時候需要檢查參數是否存在,控制器是否有parameters參數,具體實現可以看demo。

 

 

 

 本文由用戶 lxktest123 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!