背景
自以為完美的解決方案demo在此-TFMultiTabScrollView
這種界面效果需要一大段話來描述,也沒有專門的名詞,簡單說,就是簡書APP的個人簡介的樣式
同樣也是微博/Twitter的個人中心樣式??偨Y(jié)下特點就是:
- 一個頭部界面+一個分頁欄+有N個內(nèi)容分頁
- 然后每一頁可以各自上下滑動,同時可以橫向滑動切換分頁
那么這個和網(wǎng)易新聞的界面有什么區(qū)別?看上去好像一樣。
重點就在這個頭部界面,頭部上下滾動也是可以帶動內(nèi)容上下,而頭部橫向滑動卻不會帶動內(nèi)容分頁切換。
我找了下目前的一些方案,發(fā)現(xiàn)并沒有很完美的,比如嵌套UIScrollview的滑動沖突解決方案。這也是一開始我想的,似乎大家很容易走到嵌套scrollView的路線上,而這條路似乎是個死胡同。
我的方案
層級圖


- 橫向滑動用來切換分頁的scrollView大小占滿整個界面,而不只是頭部以下的位置
- N個分頁的scrollView放在橫向的ScrollView上
- 頭部放在當前顯示的那個分頁ScrollView上
這樣做可以達到:
- 橫向滾動可以切換分頁
- 頭部上下滑動可以帶動當前分頁內(nèi)容上下移動,因為頭部就在內(nèi)容分頁的scrollView上
但是還有幾個問題需要解決:
- 分頁scrollView的內(nèi)容會被頭部遮擋一部分
- 橫向切換分頁后,新的分頁頭部是不是沒有了?還是需要用3個頭部?但是用3個是不是切換起來會很難看?
- 頭部橫向滑動時,會帶動分頁橫向切換,這個效果是不需要的
解決:
- 調(diào)整分頁scrollView的contentInsert.top,讓頂部內(nèi)容空出
- 這個問題是關鍵,當初就是想到這里而否定了,而沒有走下去。解決方法就是:
- 在橫向滑動開始的時候,把頭部從分頁內(nèi)容scrollView(橙色的)上拿下來,放到橫向滑動的scrollView(綠色的)上,這時頭部就覆蓋在橙色scrollView上。
- 然后根據(jù)滑動的contentOffset.x不斷調(diào)整頭部的frame,讓它看起來沒有動
- 等到滑到目標分頁時,再把頭部又放回到當前的內(nèi)容ScrollView上。
整個過程里,視覺上看起來好像頭部視圖一直沒動。這效果就達到了。而且因為頭部視圖又放到了當前的分頁內(nèi)容ScrollView上,所以之前的效果繼續(xù)保持。
- 這是手勢沖突的問題,自定義頭部視圖的view,然后添加一個
UIPanGestureRecognizer手勢,然后實現(xiàn)手勢沖突的方法:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
//_tabContainer是橫向滑動的scrollView
if (otherGestureRecognizer.view == _tabContainer) {
return NO;
}
return YES;
}
_tabContainer是橫向滑動的scrollView,如果是和頭部的pan手勢和橫向滑動ScrollView的手勢沖突了,就返回NO。因為頭部視圖在上層,所以它的首先優(yōu)先了。這樣就把橫向滑動的手勢給屏蔽了。
這么做只是為了空白的頭部視圖不會觸發(fā)橫向內(nèi)容切換,如果在頭部視圖加入更多控件,比如再加一個UIScrollView,它本身是可以自動屏蔽橫向ScrollView(綠色)的手勢的。
這樣做就避開了scrollView嵌套滑動手勢的問題
到這主要的問題就解決了,剩下的就是分頁內(nèi)容ScrollView上下滑動時,頭部的懸浮問題了。
使用KVO檢測分頁scrollView的contentOffset,然后:
- 在向上下滑的時候,頭部跟隨上下滑,這里什么都不需要處理,因為把頭部放到了scrollView上,這是自帶效果。
- 當tab分頁欄,就是簡書例子里“動態(tài)-文章-更多”那一欄,頂?shù)搅艘晥D的頂部后,不斷調(diào)整頭部的frame.y讓他看起來不動
- 當頭部完全顯示出來后,也不斷調(diào)整frame.y讓它看起來不動
額外效果
簡書、微博和Twitter的效果是同一種,其實還有另一種類似的,就是美團的外賣商店界面,區(qū)別就是:美團這里是內(nèi)容只要上下滑動,頭部都是跟隨動的,而簡書微博這邊是只有內(nèi)容視圖滑到頂部,也就是內(nèi)容的頂接住了頭部視圖的頂?shù)臅r候,才能拖動頭部。在邏輯上,美團那種跟Safari的地址欄是一個感覺。
為了區(qū)分這兩種效果,設置了一個屬性moveHeaderOnlyContentTop,YES時就是簡書微博效果,NO就是美團外賣商店效果。默認YES。有些分頁內(nèi)容可能很少,導致tab分頁欄滑不到頂部,而如果其他分頁可以滑到頂部,這時就有一個問題,tab欄在頂部的時候,切換到內(nèi)容不足的頁面,就會導致tab分頁欄刷的一下掉下來。這效果很不好。所以我加了一個屬性
autoFillContent。如果YES,則計算內(nèi)容高度,設置contentInsert.bottom,在scrollView的底部增加一段空白。這樣內(nèi)容不足時,tab分頁欄依然可以到頂部。默認YES。有時tab分頁欄不想直接貼住視圖的頂部,比如有一個導航欄效果,內(nèi)容往上滑的時候,導航欄出來,往下滑時候呢,導航欄漸變消失。這時要給導航欄留出空位,tab分頁欄就不能直接挨著頂部,所以我加了一個屬性
topSpace,這個是用來調(diào)控tab欄和頂部的間距的。默認0。
不完美的嘗試
1. 三層嵌套scrollView
這種界面結(jié)構(gòu),很容易想到的就是3個scrollView嵌套:一個大的豎向scrollView----橫向分頁scrollView----每個分頁自身的scrollView。最外層的豎向scrollView為了是頭部能夠滑動,所以說這個頭部才是這里的癥結(jié)??!但這種結(jié)構(gòu)帶來的是兩個豎向的scrollView的滑動切換問題:
- 當tab分頁欄滑到定后,要滑動內(nèi)部的scrollView
- 到頭部除了tab欄還有更多顯示出來時,要滑動外部的scrollView來帶動頭部。
那么為什么不一直滑動外面的scrollView呢?這其實也是一種方案,只要把內(nèi)容視圖的內(nèi)容完全的展開,然后只滑動外層的scrollView來切換顯示內(nèi)容。不好的是這樣就沒法使用tableView的重用功能了,比如你有很多的cell,像簡書的動態(tài)那一欄。但分頁是可以做的,只要加載新的內(nèi)容后在繼續(xù)全部展開。但這個不完美的瑕疵讓人不爽。
一開始我以為scrollView的哪個滑動是hitView的問題,但后來發(fā)現(xiàn)scrollView的滑動是它自帶的pan手勢來控制滑動的。手勢一旦識別了,就不會再識別,除非是新的觸碰,這就是這種方案為什么要放手再重新滑動才能內(nèi)外scrollView切換。
如果scrollView的滑動不是它自帶的手勢處理,而是一個統(tǒng)一的手勢,而scrollView只要接受來自這個統(tǒng)一的手勢的滑動數(shù)據(jù)就好了。這樣只需要把數(shù)據(jù)的輸出調(diào)整成另一個scrollView就可以完成完美的滑動切換。這里就牽扯到一點設計的問題了。
2. 模擬滾動的頭部視圖
這種方案是:
整體的父視圖 ---- 橫向的scrollView ---- 分頁的豎向scrollView
整體的父視圖 ----- 頭部
也就是頭部視圖和橫向的scrollView是同一個層級上,然后頭部在上面覆蓋。
這樣要要解決的關鍵問題就是:頭部上豎向滑動,要帶動分頁內(nèi)容色scrollView滑動。而橫向滑動啊,點擊啊之類的都沒有問題了,因為它已經(jīng)不再嵌套scrollView的層級里了。
所以我自定義了一個頭部(demo里的TFScrollSimulateView),然后給它加了pan手勢來模擬scrollView的滾動,其實手指拖動是很好實現(xiàn)的,頭部的手勢拖動了多少就修改scrollView的contentOffset多少,麻煩的是:
- 手指快速的一滑,手指離開后,scrollView還要繼續(xù)滾動,而且要速度越來越慢。
- scrollView拉倒邊緣時的彈簧效果
所以我做了一個計時器,在手指離開后,不斷的計算滑動的距離,而pan手勢是有velocityInView:方法可以取得速度。這樣其實基本可以解決問題了,但有個不完美的是scrollView減速的公式只能猜:
module -= KTimerInterval * _friction * (10000 + module * module); //阻力和速度平方成正比,速度減去a*t
但是后來在一個項目里找到了相關公式:彈簧公式

3. 用圖片欺騙
其實如果不需要橫向滑動切換分頁的效果,只能點擊切換的話,這個問題的難度直線下降,因為這樣就不需要橫向的scrollView了,內(nèi)外層的scrollView可以合成一個,就完全不存在什么嵌套的問題了。
但是如果一定要有橫向切換的效果呢?可以在開始橫向滑動的時候,做兩張圖片覆蓋在內(nèi)容部分。手指滑動的時候,是圖片在切換,但是這個圖片長得跟分頁的scrollView一樣,讓人看起來好像是分頁內(nèi)容切換了一樣。
- 使用core graphic的截圖方法,在剛開始滑動的時候,把當前分頁scrollView的界面截取下來,然后覆蓋到相同位置。(就跟你把別人的桌面截一張圖,然后做成背景,再把桌面圖標全隱藏,看起來好像沒變-_-)
- 把之前的內(nèi)容圖片擺在左右,然后滑動切換時,實際是這兩個圖片在切換。
這個具體沒去實現(xiàn),只是想法,這種欺騙眼睛的手法是挺有意思的。
2017.6.28更新
修改了問題:
- 分頁
scrollView在滑動過程中會添加新的子視圖,導致遮住頭部,比如UITableView的sectionHeader. -
_currentVisableHeaderH沒有初始值導致滑動出錯 - 分頁切換時,如果頻繁點擊會導致界面閃爍
完善功能:
- 在不需要自動擴充底部內(nèi)容的時候,有可能某個分頁內(nèi)容很少,導致頭部滑動不到頂部,在切換的時候會出現(xiàn)“突然掉下來”的現(xiàn)象,現(xiàn)在改成動畫,體驗更好些。
- 做好了某個分頁滑動,其他分頁同步滑動內(nèi)容的處理。能夠達到從A分頁離開時,如果你已經(jīng)滑動到了第10行是貼著頭部視圖的底部的,在其他分頁移動了頭部視圖后,A分頁的內(nèi)容也會跟隨移動,保證回去時還是第10行貼著頭部。但是這個只有在
moveHeaderOnlyContentTop為NO的時候有效。