OC-仿抖音下拉刷新

話不多說皆辽,先上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)行了
層級關(guā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)力纹磺,謝謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帖烘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橄杨,更是在濱河造成了極大的恐慌秘症,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件式矫,死亡現(xiàn)場離奇詭異乡摹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)采转,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門聪廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞬痘,“玉大人,你說我怎么就攤上這事板熊】蛉” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵干签,是天一觀的道長津辩。 經(jīng)常有香客問我,道長容劳,這世上最難降的妖魔是什么喘沿? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮竭贩,結(jié)果婚禮上蚜印,老公的妹妹穿的比我還像新娘。我一直安慰自己留量,他們只是感情好晒哄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肪获,像睡著了一般寝凌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孝赫,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天较木,我揣著相機(jī)與錄音,去河邊找鬼青柄。 笑死伐债,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的致开。 我是一名探鬼主播峰锁,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼双戳!你這毒婦竟也來了虹蒋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤飒货,失蹤者是張志新(化名)和其女友劉穎魄衅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塘辅,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晃虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扣墩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哲银。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛吞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荆责,到底是詐尸還是另有隱情喻粹,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布草巡,位于F島的核電站守呜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏山憨。R本人自食惡果不足惜查乒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郁竟。 院中可真熱鬧玛迄,春花似錦、人聲如沸棚亩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讥蟆。三九已至勒虾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘸彤,已是汗流浹背修然。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留质况,地道東北人愕宋。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像结榄,于是被迫代替她去往敵國和親中贝。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫臼朗、插件邻寿、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,117評論 4 61
  • 飄渺世界的美渺 花兒開了,開在那映照明月的心窗下面依溯。 火紅火紅的顏色老厌,垂涎欲滴的面容。 晚風(fēng)輕輕的吹拂黎炉,習(xí)風(fēng)輕輕的...
    清照_閱讀 182評論 0 0
  • 上課時(shí),我放了一個(gè)屁--很普通的屁慷嗜。既不很臭,當(dāng)然也絕對不香淀弹。 可怕的是,教授正在講辯證法。 “請你自己對這個(gè)屁作...
  • 家里的葡萄樹架爬滿了院子庆械,濃密的樹蔭在灼灼夏日里光是看著就讓人神清氣爽薇溃。葡萄的藤蔓細(xì)細(xì)的,像雛鳥的爪兒牢牢地抓住了...
    一地荒蕪閱讀 284評論 0 0
  • 那一天缭乘, 桃花妖嬈沐序, 妖嬈迷我的眸, 眸里滿是笑意的你堕绩, 迷我的魂策幼。 那一天, 葡萄紫透奴紧, 透入香甜迷我的唇特姐, 唱...
    君兮閱讀 348評論 28 15