現(xiàn)在上市場(chǎng)上很多主流的App模塊都是很龐大,往往一個(gè)頁(yè)面會(huì)有很復(fù)雜的功能,但是再大的模塊都是由一個(gè)個(gè)簡(jiǎn)單的模塊堆疊起來(lái)的。模塊多了之后,有時(shí)要處理好模塊之間的聯(lián)系不是那種想都不用想就可以開干的。還涉及到不同模塊之間的手勢(shì)和動(dòng)畫處理。
為了舉個(gè)比較好的栗子,就拿微博個(gè)人信息界面動(dòng)動(dòng)手。通過(guò)觀察微博個(gè)人信息界面,總結(jié)出需要注意的三個(gè)地方:
- 頭部背景圖滾動(dòng)處理
- 兩層滾動(dòng)視圖嵌套手勢(shì)處理
- 按鈕底部毛毛蟲動(dòng)畫效果
頭部背景
先看下效果圖

首先第一步分析 頂部的ImageView,如果手勢(shì)向上滾動(dòng)我們會(huì)發(fā)現(xiàn)它就和 TableHeaderView差不多,當(dāng)向下拉伸直到TableView出現(xiàn)彈性效果的時(shí)候,頭部的ImageView變高了,但是不是放大。而頭像是保持不動(dòng)的。因此,頂部ImageView不是TableHeaderView,它的坐標(biāo)會(huì)隨著TableView滾動(dòng)作出不同的改變。它的位置是在TableView下面,設(shè)置
tableHeaderView為透明即可。在
scrollViewDidScroll:代理函數(shù)中實(shí)現(xiàn)(-60是它的初始y坐標(biāo))
if(offsetY <= 0){
self.coverView.y = -offsetY/2 + (- 60);
}else{
self.coverView.y = -offsetY + (- 60);
}
多重嵌套
實(shí)現(xiàn)這個(gè)布局可以是實(shí)現(xiàn)一個(gè)TabView,把四個(gè)子視圖作為TabView的子控件, TabView作為TableView的cell顯示出來(lái)。但是當(dāng)我們照做的時(shí)候發(fā)現(xiàn),當(dāng)滾動(dòng)TableView到底部的時(shí)候,TabView的子視圖不會(huì)滾動(dòng),而滾動(dòng)TabView的子視圖到頂部,TableView也不會(huì)滾動(dòng)。之所以會(huì)出現(xiàn)這樣的情況是因?yàn)樽涌丶透缚丶謩?shì)是單獨(dú)響應(yīng)不會(huì)傳遞。解決的方法是自定義一個(gè)UITableView重寫以下方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
這個(gè)方法返回YES可以讓子控件接收到父控件的手勢(shì)事件,子控件的手勢(shì)也會(huì)傳給父控件,在這里可以讓UITableView的滾動(dòng)事件傳遞。
但是實(shí)現(xiàn)后發(fā)現(xiàn) 滾動(dòng)四個(gè)子控件 父控件也跟著滾動(dòng)。我們要實(shí)現(xiàn)的是:當(dāng)父控件滾動(dòng)到底部時(shí)子控件才能滾動(dòng),子控件滾動(dòng)到頭部時(shí),子控件停止?jié)L動(dòng)父控件開始滾動(dòng)。邏輯代碼如下:
父控件:
if(offsetY >= HeaderHeight - 64){
[[NSNotificationCenter defaultCenter] postNotificationName:TabViewScrollToTopNotification object:@(YES)];
self.shouldScroll = NO;
}else{
[[NSNotificationCenter defaultCenter] postNotificationName:TabViewScrollToTopNotification object:@(NO)];
}
if(self.shouldScroll == NO){
[scrollView setContentOffset:CGPointMake(0, HeaderHeight - 64)];
}
子控件:
CGFloat offsetY = scrollView.contentOffset.y;
if(offsetY <= 0){
[[NSNotificationCenter defaultCenter] postNotificationName:ItemScrollToTopNotification object:@(YES)];
}
if(self.shouldScroll == NO){
[scrollView setContentOffset:CGPointZero];
}
毛毛蟲效果
實(shí)現(xiàn)毛毛蟲動(dòng)畫效果可有多種方式,在這里我用的是CASharpLayer結(jié)合UIBezierPath來(lái)實(shí)現(xiàn)。
仔細(xì)觀察動(dòng)畫可發(fā)現(xiàn),動(dòng)畫可以切分為兩部完成,如下圖所示:

通過(guò)改變
CASharpLayer的strokeStart和strokeEnd屬性來(lái)控制長(zhǎng)度即可。
首先, 在初始化布局每個(gè)標(biāo)題按鈕的時(shí)候,用一個(gè)字典把所有按鈕下面的坐標(biāo)記錄下來(lái)
- (void)setupBtn:(UIButton *)btn index:(NSInteger)index{
if(index == 0){
btn.selected = YES;
self.selectBtn = btn;
}
self.btnDict[@(index)] = btn;
self.pointsDict[@(index)] = @{@"start":@(btn.x + 2),@"end":@(CGRectGetMaxX(btn.frame)-2)};
btn.tag = index;
/**
. 其他設(shè)置
......
*/
}
然后通過(guò)監(jiān)控scrollView的偏移量,結(jié)合保存的坐標(biāo)計(jì)算出在不同位置的下標(biāo)線的strokeStart和strokeEnd
- (void)LHTabViewDidScroll:(LHTabView *)tabView{
CGFloat offsetX = tabView.offset.x;
NSInteger index = offsetX/WIDTH;
CGFloat zero = [self.pointsDict[@(0)][@"start"] floatValue];
CGFloat currentStart = [self.pointsDict[@(index)][@"start"] floatValue];
CGFloat currentEnd = [self.pointsDict[@(index)][@"end"] floatValue];
CGFloat nextStart = [self.pointsDict[@(index+1)][@"start"] floatValue];
CGFloat nextEnd = [self.pointsDict[@(index+1)][@"end"] floatValue];
CGFloat PhysicsDelta = offsetX - index * WIDTH;
CGFloat end,start;
CGFloat delta = nextEnd - currentEnd;
if(PhysicsDelta <= WIDTH/2){ //對(duì)應(yīng)圖片 step one
delta = (PhysicsDelta/(WIDTH/2)) * delta;
end = currentEnd + delta;
self.lineLayer.strokeStart = (currentStart - zero)/self.lineTotalWidth;
self.lineLayer.strokeEnd = (end - zero)/self.lineTotalWidth;
}else{ //對(duì)應(yīng)圖片 step two
delta = nextStart - currentStart;
PhysicsDelta = PhysicsDelta - WIDTH/2;
delta = (PhysicsDelta/(WIDTH/2)) * delta;
start = currentStart + delta;
self.lineLayer.strokeStart = (start - zero)/self.lineTotalWidth;
self.lineLayer.strokeEnd = (nextEnd - zero)/self.lineTotalWidth;
}
}
剛開始的時(shí)候發(fā)現(xiàn)CAShaplayer滑動(dòng)的效果和scrollView的滑動(dòng)有延遲,可以通過(guò)CAShaplayer的speed屬性來(lái)調(diào)整動(dòng)畫速率即可 。
實(shí)現(xiàn)效果圖如下:

以上就是所有的步驟。具體代碼可以點(diǎn)擊demo
最后吐槽下,因?yàn)樽罱谑褂?a target="_blank" rel="nofollow">react-native做一個(gè)項(xiàng)目,需要實(shí)現(xiàn)類似功能,但是因?yàn)榛瑒?dòng)的效果沒有原生的流暢,出現(xiàn)很奇怪的現(xiàn)象,各種不順,沒辦法只能放棄使用這種UI結(jié)構(gòu)。看來(lái)react-native還有很長(zhǎng)的路要走,原生還是第一生產(chǎn)力。