平常遇到大多數的帶有列表的應用都會遇到這個場景:在列表頂端有一個Header,當向上滑動列表時,壓縮header,向下滑動列表到頭時,展開header。這種樣式在例如微博,twitter這些展示動態的界面里很常見。這種效果怎么實現呢?下面介紹我用的方法。
新博客:wossoneri.com
先看一下效果圖
首先看一下BiliBili客戶端的視頻瀏覽界面。默認界面Header完全展開,並且Header顯示AV號(別亂想,就是視頻編號了)以及播放按鈕。滑動之后Header被壓縮,按鈕移到AV號左邊。

我就照着界面簡單實現了主要功能,比較簡陋。對於按鈕移動的動畫就沒有去花時間還原了,畢竟這里主要是為了實現滾動壓縮、展開Header,動畫不討論。

實現思路
如圖所示:

首先在要將該界面分成兩部分:一個ScrollHeader,一個UITableView。
- ScrollHeader占據屏幕上方,高度為展開后的高度
- UITableView占據整個屏幕,這樣可以完全滾動。為了讓內容不被
ScrollHeader遮蓋,設置contentOffset屬性即可
我這里用的
ScrollHeader是作為獨立的控件使用,與UITableView的HeaderView並無關系
之后將ScrollHeader分成兩部分:topView和bottomView。
- topView 即為壓縮后的布局
- bottomView 即為展開后的布局
我這里采取將topView固定在ScrollHeader的頂部,覆蓋在bottomView上方,根據滑動對其淡入淡出。
另一種效果是把
topView與bottomView上下連接在一起,也就是沒有覆蓋關系,然后當bottomView向上滑時topView從屏幕外滑入屏幕內。這個讀者可以嘗試着實現一下。
實現方法
首先按照前面的設計將界面布局好,之后的重點是為ScrollHeader增加滑動效果。
由於我的ScrollHeader繼承的是UIView,所以為了處理滑動,為其設置一個UIScrollView
@property (nonatomic, strong) UIScrollView *headerScrollView;
這個屬性的作用就是獲得UITableView對應的scrollView,因為UITableView本身是繼承UIScrollView的,所以在初始化ScrollHeader的時候可以這么寫:
MyScrollHeader header = [[MyScrollHeader alloc] init];
header.headerScrollView = _tableView;
這樣,在ScrollHeader中就可以通過headerScrollView來判斷滑動狀態了。
剩下的工作就是捕捉滑動狀態,並且對滑動距離進行計算,移動topView和bottomView了。對於計算也不做過多說明了,因為沒有幾張草圖也說不清。直接貼上代碼,跟着代碼算一下就知道怎么回事了。
#pragma mark - scroll state
-(void)willMoveToSuperview:(UIView *)newSuperview{
[self.headerScrollView addObserver:self forKeyPath:@"contentOffset" options:(NSKeyValueObservingOptionNew) context:Nil];
self.headerScrollView.contentInset = UIEdgeInsetsMake(_bottomHeight, 0, 0, 0); // tableview 偏移
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ //監視滑動
CGPoint newOffset = [change[@"new"] CGPointValue];
[self updateSubViewsWithScrollOffset:newOffset];
}
-(void)updateSubViewsWithScrollOffset:(CGPoint)newOffset {
// NSLog(@"scrollview inset top:%f", self.headerScrollView.contentInset.top);
// NSLog(@"new offset before:%f", newOffset.y);
// NSLog(@"newOffset : %f", newOffset.y);
float startChangeOffset = - self.headerScrollView.contentInset.top;
newOffset = CGPointMake(newOffset.x, newOffset.y < startChangeOffset ? startChangeOffset : (newOffset.y > _destinaOffset ? _destinaOffset : newOffset.y));
// NSLog(@"new offset after:%f", newOffset.y);
float newY = - newOffset.y - _bottomHeight;
float d = _destinaOffset - startChangeOffset;
float alpha = 1 - (newOffset.y - startChangeOffset) / d;
self.frame = CGRectMake(0, newY, self.frame.size.width, self.frame.size.height);
topView.frame = CGRectMake(0, -newY, self.frame.size.width, self.frame.size.height);
topView.alpha = 1 - alpha;
bottomView.alpha = alpha;
_currentOffset = newOffset.y;
NSLog(@"current offset: %f", _currentOffset);
}
最后放上源碼
粗略寫的,代碼是用自動布局寫的。往后抽空會優化一下代碼,把ScrollHeader封裝成控件方便使用。
