現(xiàn)在上市場(chǎng)上很多主流的App模塊都是很龐大榨汤,往往一個(gè)頁(yè)面會(huì)有很復(fù)雜的功能惶楼,但是再大的模塊都是由一個(gè)個(gè)簡(jiǎn)單的模塊堆疊起來(lái)的。模塊多了之后疼燥,有時(shí)要處理好模塊之間的聯(lián)系不是那種想都不用想就可以開(kāi)干的沧卢。還涉及到不同模塊之間的手勢(shì)和動(dòng)畫(huà)處理。
為了舉個(gè)比較好的栗子醉者,就拿微博個(gè)人信息界面動(dòng)動(dòng)手但狭。通過(guò)觀察微博個(gè)人信息界面,總結(jié)出需要注意的三個(gè)地方:
- 頭部背景圖滾動(dòng)處理
- 兩層滾動(dòng)視圖嵌套手勢(shì)處理
- 按鈕底部毛毛蟲(chóng)動(dòng)畫(huà)效果
頭部背景
先看下效果圖
首先第一步分析 頂部的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è)子視圖作為T(mén)abView的子控件, 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
重寫(xiě)以下方法
- (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)父控件開(kāi)始滾動(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];
}
毛毛蟲(chóng)效果
實(shí)現(xiàn)毛毛蟲(chóng)動(dòng)畫(huà)效果可有多種方式倍靡,在這里我用的是CASharpLayer
結(jié)合UIBezierPath
來(lái)實(shí)現(xiàn)猴伶。
仔細(xì)觀察動(dòng)畫(huà)可發(fā)現(xiàn),動(dòng)畫(huà)可以切分為兩部完成塌西,如下圖所示:
通過(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)線(xiàn)的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;
}
}
剛開(kāi)始的時(shí)候發(fā)現(xiàn)CAShaplayer滑動(dòng)的效果和scrollView的滑動(dòng)有延遲,可以通過(guò)CAShaplayer的speed
屬性來(lái)調(diào)整動(dòng)畫(huà)速率即可 站辉。
實(shí)現(xiàn)效果圖如下:
以上就是所有的步驟呢撞。具體代碼可以點(diǎn)擊demo
最后吐槽下,因?yàn)樽罱谑褂?a target="_blank" rel="nofollow">react-native做一個(gè)項(xiàng)目饰剥,需要實(shí)現(xiàn)類(lèi)似功能殊霞,但是因?yàn)榛瑒?dòng)的效果沒(méi)有原生的流暢,出現(xiàn)很奇怪的現(xiàn)象汰蓉,各種不順绷蹲,沒(méi)辦法只能放棄使用這種UI結(jié)構(gòu)。看來(lái)react-native還有很長(zhǎng)的路要走祝钢,原生還是第一生產(chǎn)力比规。