話不多說皆辽,先上DEMO記得star哦
既然是仿抖音效果,那首先就是要分析這個(gè)效果的實(shí)現(xiàn)思路,根據(jù)觀察屈藐,實(shí)現(xiàn)思路大致如下(如果你有什么更好的方案也不妨告訴我哦,交流使人進(jìn)步):
1乐严、上拉時(shí)頁面有翻頁效果艇劫,可以用scrollview的pagingEnabled來實(shí)現(xiàn)留储,也就是說列表頁不管你用tableview還是collectionview态秧,只要每個(gè)cell是全屏的就可以
2董虱、下拉:當(dāng)頁面不是停留在第一個(gè)cell時(shí),下拉就只是scrollView的滾動(dòng)效果,不會觸發(fā)刷新愤诱,當(dāng)頁面停留在第一個(gè)cell云头,也就是說scrollView.contentOffset.y = 0的時(shí)候,手指下拉才會觸發(fā)刷新效果淫半,并且下拉時(shí)scrollView不動(dòng)溃槐,也就是沒有scrollview的彈性效果,因此scrollView.bounces = NO
3科吭、既然下拉時(shí)scrollView不動(dòng)昏滴,就不能使用代理來監(jiān)聽scrollView的滑動(dòng)實(shí)現(xiàn)刷新,于是我想到了用touches的系列方法來監(jiān)控手指下滑位移;
4对人、動(dòng)畫分解有五步:
(1)下拉時(shí)“推薦谣殊、附近”的那個(gè)導(dǎo)航條和“下拉刷新內(nèi)容”的視圖有漸隱漸顯的效果,位置也隨著手指下移规伐,可以通過手指下滑位移計(jì)算alpha來實(shí)現(xiàn)
(2)下拉時(shí)蟹倾,“下拉刷新內(nèi)容”的視圖右邊那個(gè)有缺口的小圓環(huán)會隨著手指滑動(dòng)轉(zhuǎn)圈匣缘,下滑時(shí)逆時(shí)針旋轉(zhuǎn)
(3)下滑一定距離后如果不松手猖闪,又繼續(xù)上滑,會執(zhí)行前兩步的反效果肌厨,圓環(huán)順時(shí)針旋轉(zhuǎn)培慌,手指停在屏幕上,圓環(huán)就停止轉(zhuǎn)動(dòng)
(4)下滑到某個(gè)臨界點(diǎn)柑爸,導(dǎo)航條和刷新視圖都不再移動(dòng)(此時(shí)導(dǎo)航條已經(jīng)完全透明)吵护,所以可以通過計(jì)算起始點(diǎn)和當(dāng)前點(diǎn)移動(dòng)距離來計(jì)算透明度、位移表鳍、旋轉(zhuǎn)角度馅而,這些操作都在touchesMoved中實(shí)現(xiàn)
(5)到臨界點(diǎn)松手后,導(dǎo)航條和刷新視圖都回到原始位置譬圣,小圓環(huán)一直順時(shí)針轉(zhuǎn)圈瓮恭,直到刷新結(jié)束,停止動(dòng)畫厘熟,隱藏刷新視圖屯蹦,顯示導(dǎo)航條,如果沒達(dá)到臨界點(diǎn)就松手绳姨,不會觸發(fā)刷新
描述的有點(diǎn)多登澜,但是只有仔細(xì)分析了才能有個(gè)清晰的思路,實(shí)現(xiàn)的時(shí)候也就會少走一些彎路飘庄。寫代碼最忌拿到功能還沒想好就開始干脑蠕,結(jié)果實(shí)現(xiàn)的時(shí)候遇到太多的坑,反反復(fù)復(fù)浪費(fèi)時(shí)間跪削。
好了空郊,思路整理了之后那么就一步步實(shí)現(xiàn)吧
一份招、基礎(chǔ)功能
創(chuàng)建tableview、mainViewNavigitionView(導(dǎo)航條)狞甚、RefreshNavigitionView(刷新視圖锁摔,初始alpha為0)、startPoint(起始觸摸點(diǎn))哼审,基本樣式都寫完之后就開始運(yùn)行了運(yùn)行起來大面上一看谐腰,嗯,長得還挺像的涩盾,上拉翻頁也沒問題十气,但是,重點(diǎn)來了:
我手指下滑的時(shí)候touchesBegan等系列方法根本就沒走春霍,what?這怎么辦砸西,說好的監(jiān)聽手指移動(dòng)距離的,方法都不走我怎么監(jiān)聽址儒?
經(jīng)過一番搜索查證芹枷,原來是事件響應(yīng)鏈的問題,當(dāng)我們點(diǎn)擊屏幕時(shí)莲趣,第一響應(yīng)者應(yīng)該是UITableView鸳慈,而我們調(diào)用的touchBegan其實(shí)是ViewController的View的方法,所以無法被調(diào)用喧伞,如果不了解的話下面兩篇文章可以幫到你:
從iOS的事件響應(yīng)鏈看TableView為什么不響應(yīng)touchesBegan
讓UITableView響應(yīng)touch事件
根據(jù)文中方法走芋,我給TableView寫了個(gè)基類,添加了touches相關(guān)的一些代理方法潘鲫,運(yùn)行起來翁逞,終于可以監(jiān)聽手指移動(dòng)了
但是,問題又來了溉仑,我在touchesMoved打印了手指觸摸點(diǎn)的y值挖函,我發(fā)現(xiàn)手指滑動(dòng)一會兒后控制臺就不再打印了,每次位移大概十幾個(gè)像素彼念,并且松手后touchesEnded方法也不怎么走(這個(gè)方法不太靈光芭不)
于是我把TableView先注掉,讓手指直接觸摸在self.view上逐沙,看看touches方法是否正常哲思,事實(shí)證明是沒問題的
把tableView解開依然不好使(不好使的原因我還沒有深究,如果有人知道吩案,不妨告訴我啦棚赔,謝謝),既然手指直接摸在self.view上是好使的,而且touchBegan其實(shí)是ViewController的View的方法靠益,那我是不是可以在tableView上面覆蓋一層透明的view丧肴,通過滑動(dòng)判斷來隱藏和顯示它,實(shí)現(xiàn)下拉刷新胧后,上拉翻頁(上拉時(shí)隱藏view芋浮,手指就摸在tableView上,就是拖拽手勢了)
二壳快、動(dòng)畫效果
根據(jù)上面的想法纸巷,初步實(shí)現(xiàn)了手指觸摸的系列操作,但是還有許多細(xì)節(jié)需要注意眶痰,就是clearview的隱藏和顯示的臨界點(diǎn)瘤旨,思路如下:
1、頁面初始竖伯,clearview顯示存哲,但背景色是透明的,用戶看不到七婴,判斷手指滑動(dòng)位移祟偷,如果是下拉,就執(zhí)行下拉刷新的那些操作本姥,以及動(dòng)畫肩袍,如果是上拉杭棵,上拉到某個(gè)臨界點(diǎn)婚惫,就翻頁,并且隱藏clearview魂爪,這樣用戶下次下拉的時(shí)候就不會觸發(fā)touch的方法先舷,而是tableView的向下拖拽翻頁
2、監(jiān)聽tableView的滑動(dòng)滓侍,如果滑動(dòng)到第一個(gè)cell停止了蒋川,就要讓clearview顯示,有可能用戶會繼續(xù)下滑撩笆,就會觸發(fā)touch的方法捺球,執(zhí)行1的操作
3、觸摸結(jié)束時(shí)夕冲,需要恢復(fù)導(dǎo)航條和刷新視圖的frame,如果此時(shí)RefreshNavigitionView的alpha不為1氮兵,說明沒有下拉到臨界點(diǎn),各自透明度也要恢復(fù)到初始狀態(tài)歹鱼,如果是1泣栈,就要走刷新的回調(diào)
到這里基本上上拉下拉的操作都可以順暢完成了,接下來就該實(shí)現(xiàn)動(dòng)畫了,frame的移動(dòng)南片,以及松手后圓環(huán)一直轉(zhuǎn)圈這些都好做掺涛,困住我的是手指下拉時(shí)圓環(huán)隨著手指下滑位移旋轉(zhuǎn),也就是說它既要隨著父視圖RefreshNavigitionView下移疼进,還要以自己為中心旋轉(zhuǎn)薪缆,手指滑動(dòng)它就轉(zhuǎn),手指不動(dòng)它就不轉(zhuǎn)
旋轉(zhuǎn)動(dòng)畫我選的是transform伞广,松手后圓環(huán)旋轉(zhuǎn)用的是CABasicAnimation矮燎,但它是layer動(dòng)畫,動(dòng)畫結(jié)束后會復(fù)位赔癌,實(shí)際上view本身沒有轉(zhuǎn)動(dòng)诞外,使用過程中就會出現(xiàn)圓環(huán)轉(zhuǎn)一下回去又轉(zhuǎn)一下又回去的卡頓現(xiàn)象(當(dāng)然也可以用代碼讓它不要復(fù)位:CABasicAnimation使用總結(jié) 比較麻煩,代碼也比transform多灾票,transform只需一行代碼即可旋轉(zhuǎn))
transform是疊加效果峡谊,可以根據(jù)上次旋轉(zhuǎn)的角度繼續(xù)旋轉(zhuǎn),如果我把度數(shù)寫成固定值刊苍,那么圓環(huán)就會隨著手指移動(dòng)均勻旋轉(zhuǎn)既们,動(dòng)畫也比較流暢
理想很豐滿,現(xiàn)實(shí)很殘酷呀正什。transform動(dòng)畫寫上之后啥纸,圓環(huán)居然隨著手指移動(dòng)亂轉(zhuǎn),一會放大婴氮,一會縮小斯棒,一會翻轉(zhuǎn),網(wǎng)上查了各種transform的使用方法主经,我寫的沒問題啊荣暮,著實(shí)困了我不少時(shí)間,只好求助小伙伴了罩驻。經(jīng)查證是自動(dòng)布局的鍋穗酥,transform是frame動(dòng)畫,需要圓環(huán)確切的frame,而我用的是SDAutolayout惠遏,就算updatelayout也不好使砾跃,小圓環(huán)的位置如果改成frame,動(dòng)畫就沒問題了节吮,然后又試了masonry,也是好使的抽高,所以說有時(shí)候老框架的優(yōu)勢還是很明顯的
核心代碼如下,注釋寫的很清楚:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.scrollView.contentOffset.y <=0&&self.refreshStatus == REFRESH_Normal) {
//當(dāng)tableview停在第一個(gè)cell并且是正常狀態(tài)才記錄起始觸摸點(diǎn),防止頁面在刷新時(shí)用戶再次向下拖拽頁面造成多次下拉刷新
startPoint = [touches.anyObject locationInView:self.view];
NSLog(@"startPoint:%.f",startPoint.y);
}else{
//否則就隱藏透明視圖课锌,讓頁面能響應(yīng)tableview的拖拽手勢
_clearView.hidden = YES;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (CGPointEqualToPoint(startPoint,CGPointZero)) {
//沒記錄到起始觸摸點(diǎn)就返回
return;
}
CGPoint currentPoint = [touches.anyObject locationInView:self.view];
float moveDistance = currentPoint.y-startPoint.y;
if (self.scrollView.contentOffset.y <=0)
{
//根據(jù)觸摸點(diǎn)移動(dòng)方向判斷用戶是下拉還是上拉
if(moveDistance>0&&moveDistance<MaxDistance) {
self.refreshStatus = REFRESH_MoveDown;
//只判斷當(dāng)前觸摸點(diǎn)與起始觸摸點(diǎn)y軸方向的移動(dòng)距離厨内,只要y比起始觸摸點(diǎn)的y大就證明是下拉祈秕,這中間可能存在先下拉一段距離沒松手又上滑了一點(diǎn)的情況
float alpha = moveDistance/MaxDistance;
//moveDistance>0則是下拉刷新,在下拉距離小于MaxDistance的時(shí)候?qū)refreshNavigitionView和_mainViewNavigitionView進(jìn)行透明度雏胃、frame移動(dòng)操作
_refreshNavigitionView.alpha = alpha;
CGRect frame = _refreshNavigitionView.frame;
frame.origin.y = moveDistance;
_refreshNavigitionView.frame = frame;
if (_mainViewNavigitionView) {
_mainViewNavigitionView.alpha = 1-alpha;
frame = _mainViewNavigitionView.frame;
frame.origin.y = moveDistance;
_mainViewNavigitionView.frame = frame;
}
//在整體判斷為下拉刷新的情況下请毛,還需要對上一個(gè)觸摸點(diǎn)和當(dāng)前觸摸點(diǎn)進(jìn)行比對,判斷圓圈旋轉(zhuǎn)方向瞭亮,下移逆時(shí)針方仿,上移順時(shí)針
CGPoint previousPoint = [touches.anyObject previousLocationInView:self.view];//上一個(gè)坐標(biāo)
if (currentPoint.y>previousPoint.y) {
_refreshNavigitionView.circleImage.transform= CGAffineTransformRotate(_refreshNavigitionView.circleImage.transform,-0.08);
}else
_refreshNavigitionView.circleImage.transform= CGAffineTransformRotate(_refreshNavigitionView.circleImage.transform,0.08);
}
else if(moveDistance>=MaxDistance)
{
self.refreshStatus = REFRESH_MoveDown;
//下拉到最大點(diǎn)之后,_refreshNavigitionView和_mainViewNavigitionView就保持透明度和位置统翩,不再移動(dòng)
_refreshNavigitionView.alpha = 1;
if (_mainViewNavigitionView) {
_mainViewNavigitionView.alpha = 0;
}
}else if(moveDistance<0)
{
self.refreshStatus = REFRESH_MoveUp;
//moveDistance<0則是上拉 根據(jù)移動(dòng)距離修改tableview.contentOffset仙蚜,模仿tableview的拖拽效果,一旦執(zhí)行了這行代碼厂汗,下個(gè)觸摸點(diǎn)就會走外層else代碼
self.scrollView.contentOffset = CGPointMake(0, -moveDistance);
}
}else{
self.refreshStatus = REFRESH_MoveUp;
//tableview被上拉了
moveDistance = startPoint.y - currentPoint.y;//轉(zhuǎn)換為正數(shù)
if (moveDistance>MaxScroll) {
//上拉距離超過MaxScroll委粉,就讓tableview滾動(dòng)到第二個(gè)cell,模仿tableview翻頁效果
_clearView.hidden = YES;
//[self.tableview scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
[UIView animateWithDuration:0.3 animations:^{
self.scrollView.contentOffset = CGPointMake(0, kHeight);
}];
}else if(moveDistance>0&&moveDistance<MaxScroll){
self.scrollView.contentOffset = CGPointMake(0, moveDistance);
}
}
}
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event
{
//清楚起始觸摸點(diǎn)
startPoint = CGPointZero;
//觸摸結(jié)束恢復(fù)原位-松手回彈
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = _refreshNavigitionView.frame;
frame.origin.y = 0;
_refreshNavigitionView.frame = frame;
if (_mainViewNavigitionView) {
frame = _mainViewNavigitionView.frame;
frame.origin.y = 0;
_mainViewNavigitionView.frame = frame;
}
if (self.scrollView.contentOffset.y<MaxScroll) {
//沒滾動(dòng)到最大點(diǎn)娶桦,就復(fù)原tableview的位置
self.scrollView.contentOffset = CGPointMake(0, 0);
}
}];
//_refreshNavigitionView.alpha=1的時(shí)候說明用戶拖拽到最大點(diǎn)贾节,可以開始刷新頁面
if (_refreshNavigitionView.alpha == 1) {
self.refreshStatus = XDREFRESH_BeginRefresh;
//刷新圖片
[self startAnimation];
if (self.refreshBlock) {
self.refreshBlock();
}
}else
{
//沒下拉到最大點(diǎn),alpha復(fù)原
[self resumeNormal];
}
}
使用方法很簡單衷畦,下載工程后栗涂,將JXRefresh文件夾拖入工程,vc繼承JXRefreshViewController祈争,然后寫下列代碼即可
__weak typeof(self) weakSelf = self;
[self addJXRefreshWithTableView:self.tableview andNavView:self.mainViewNavigitionView andRefreshBlock:^{
//此處寫你刷新請求的方法斤程,我這里是模擬刷新
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf endRefresh];
});
}];
注意點(diǎn):使用時(shí)只需要初始化tableview 和mainViewNavigitionView就好,不要添加到self.view上
代碼質(zhì)量和封裝效果差點(diǎn)(我還是有自知之明的)菩混,肯定可以有更優(yōu)的實(shí)現(xiàn)效果的忿墅,可以參照下思路呀,有問題及時(shí)反饋哈
如果覺得對您有用墨吓,點(diǎn)贊打賞關(guān)注一下唄球匕,^?_?^你們的支持是我最大的動(dòng)力纹磺,謝謝