平常遇到大多數的帶有列表的應用都會遇到這個場景:在列表頂端有一個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
封裝成控件方便使用。