正確處理iOS從下方滑出滾動檢視
在iOS 11開始,從最早的地圖應用到最近的捷徑,陸續有系統應用使用從下方滑出列表的形式,這種系統提供的圓角風格檢視用手勢劃出和隱藏時非常自然流暢。國內的一些應用也跟進了這種互動方式,但是我發現很大一部分APP都沒有正確的處理ScrollView滾動和檢視滾動的銜接,以至於相比於系統應用不夠自然。比如知乎的評論列表頁,需要手指拿開一下才能切換檢視移動和scroll滾動,銜接不夠連續。
此文希望幫到你能正確處理類似這種響應物件的切換,讓你的應用和系統應用一樣自然流暢。
(系統應用的滑出檢視)
(Demo跑出來的效果,壓縮了解析度,保留原始幀率)
因為此場景下有2種滑動,一種是檢視向上移動ScrollView不滾動,另一種模式是ScrollView滑動而檢視不移動,我們可能很自然的想到另外新增一個手勢,禁止ScrollView滾動,然後再去驅動檢視向上移動,在不需要的時候再去禁止這個手勢和開啟ScrollView滾動。如果這麼處理,可能就會發現永遠也得不到一個銜接自然的滾動體驗。
我的方法是不要禁止ScrollView的手勢,但是可以取消ScrollView滾動:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { scrollView.contentOffset = CGPointMake(0, 0); }
另外如果你理解了ScrollView的實現,那麼就不用再另外新增手勢了,因為本身ScrollView也是由手勢驅動的,而我們可以直接得到scrollView.panGestureRecognizer:
[scrollView.panGestureRecognizer addTarget:self action:@selector(panGestureHandle:)];
大部分事物處理就將交給panGestureHandle處理了:
// 核心函式:手勢處理 - (void)panGestureHandle:(UIPanGestureRecognizer *)tap { static CGPoint startPoint; static CGPoint viewPoint; static BOOL isBegan; CGPoint endPoint; if ((self.tableView.contentOffset.y > 0 && self.sizeState == SlideScrollViewStateFull) || self.top < FULL_TOP) { isBegan = NO; [self panGestureEndWithViewPoint:viewPoint]; return; } _scrollDecelerat = NO; self.tableView.showsVerticalScrollIndicator = NO; if (tap.state == UIGestureRecognizerStateBegan || isBegan == NO) { isBegan = YES; startPoint = [tap locationInView:self.superview]; viewPoint = self.origin; } switch (tap.state) { case UIGestureRecognizerStateChanged: { endPoint = [tap locationInView:self.superview]; CGFloat toPointY = viewPoint.y + (endPoint.y - startPoint.y); self.top = toPointY; } break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateFailed: { isBegan = NO; [self panGestureEndWithViewPoint:viewPoint]; } default: break; } }
這裡有需要注意的地方就是我們需要手動計算一次手勢移動的距離,因為一個手勢可能前一部分在響應ScrollView的滾動,而後一部分又切換到了移動檢視,所以開始移動的點可能並不是手勢開始的時候。
如果還有非ScrollView區域,這時候就可以另外新增一個手勢到這個區域檢視上了,如demo中的搜尋框部分,處理函式和上面一樣即可。
有更多細節比如使ScrollView取消慣性滾動等,可以馬上檢視Demo瞭解,這裡就不一一列舉了。