MJRefresh記錄

一天

背景

寫這篇文章主要有兩個目的:

  1. 雖然refresh的源碼已經有很多小伙伴分析過了。但是其應用不能說不夠廣闊。所以,抽空重新梳理了一下該源碼辐啄,然后記錄一下自己的心得采章。方便之后重新閱讀,然后對比更新壶辜。
  2. 另一個問題是發(fā)現(xiàn)一個閱讀源碼的現(xiàn)象:比如源碼有100個文件悯舟,當閱讀了10個文件,就說我閱讀過某某某框架的源碼砸民,然后看到源碼中某某方法和另一方法實現(xiàn)一致图谷,然后就直接調用同一個方法。但是此時就很容易出現(xiàn)問題阱洪。有時和你理論的時候便贵,依舊意識不到問題所在,繼續(xù)翻開源碼說實現(xiàn)是一致的冗荸〕辛В可源碼整體看一下,就能發(fā)現(xiàn)遇到的問題蚌本。最后一種情況將會分析這遇到的問題盔粹。

本文從以下幾個方面進行記錄:

  • 繼承層次
  • 整體分析
  • 各類的職責
  • 下拉刷新
    • 主動調用代碼
    • UI進行交互
  • 上拉刷新
  • 常見的錯誤?

一、基本類繼承層次關系

// 為了方便表示程癌,使用~代替
~ = MJRefresh

~Component: UIView
    ~Header
        ~StateHeader
            ~NormalHeader
            ~GifHeader
    ~Footer
        ~AutoFooter
            ~AutoStateFooter
                ~AutoNormalFooter   
                ~AutoGifFooter
        ~BackFooter 
            ~BackStateFooter
                ~BackNormalFooter
                ~BackGifFooter

二舷嗡、整體分析:

  1. MJRefreshComponet是繼承自UIView。其他所有的類都是其子類嵌莉。
  2. mj_header和mj_footer是添加在ScrollView上分類的屬性进萄,其類型是Header/Footer類型,并且其添加到了scrollview的view容器內锐峭。因此在賦值后中鼠,才會出現(xiàn)在頭部/尾部。
  3. 通篇控制的方式沿癞,是通過子類重寫其基類的MJRefreshState類型的state屬性援雇,重寫其set方法進行更改當前刷新的狀態(tài)。從而控制頭部和尾部的展示內容(是否隱藏椎扬,內邊距惫搏,偏移量等)。最終匯聚成看到的展示樣貌蚕涤。

三筐赔、各類的職責

要看清各個部件是如何進行邏輯的組織及代碼的編寫,得分清各個類的職責是什么钻趋,負責哪一模塊川陆,然后才能更好地理通整個源碼的思想。

  1. ~State模塊主要處理狀態(tài):時間和不同狀態(tài)的title
  2. ~Normal模塊主要處理:箭頭的方向,是否顯示及l(fā)oadingView的是否顯示较沪。
  3. ~Component基模塊:主要暴露相應的接口鳞绕,block供給子類進行組裝及監(jiān)聽等的處理。
  4. ~Header 尸曼、~Footer模塊:初始化的操作们何、設置frame的高度、scrollview的布局操作(inset, offset等)控轿。

四冤竹、Header

下拉刷新刷新的方式有兩種:一種是首次進入頁面的時候,就主動觸發(fā)代碼刷新的操作茬射,使其開始下拉刷新鹦蠕。另一種是手動下拉scrollview,進行的一系列UI操作及其給出的反饋在抛。

主動調用代碼進行的刷新操作

調用流程:

  • -[MJRefreshHeader beginRefreshing]

    1. setState:(refreshing)
    2. 展示刷新樣式(增加滾動區(qū)域钟病,更新offest值為header的高度)
    3. 執(zhí)行block內部的刷新請求(executeRefreshingCallback)。
    4. 當3中的請求回調操作執(zhí)行完之后刚梭,當外部主動調用- endRefreshing時進行還原操作肠阱。
  • -[MJRefreshHeader endRefreshing]

    1. setState:(idle)
    2. 恢復刷新樣式,頭部消失(更改inset為初始值)
    3. 如果endrefreshing中有回調時就執(zhí)行,沒有就結束整個流程朴读。

與UI進行交互出現(xiàn)的刷新操作

主要有通過下拉或者上滑出現(xiàn)的UI變動及刷新的邏輯等屹徘。后半部分是一致的。主要分析前半部分與UI進行交互衅金,然后進行更新頭部的動畫的過程噪伊。

以下拉刷新為例:(其中h為header的高度)

  1. 當下拉scrollview時,觸發(fā)其delegate方法:scrollViewContentOffsetDidChange:方法典挑。

  2. 當drag == true 時酥宴,state狀態(tài)一直在變化啦吧;

    • 下拉到臨界值:
      (state == idle && offsetY < -h)時 --> state = pulling您觉。此時狀態(tài)文字要發(fā)生變化,箭頭也要進行翻轉授滓。根據(jù)各類的職責去相應的類中更改即可琳水。

    • 上拉到臨界值:
      state == pulling && offsetY >= -h時,恢復state = idle般堆。同時恢復箭頭在孝,文字等的狀態(tài)。

  3. 當drag == false && state == pulling淮摔,即在拉過臨界值私沮,并且松手了,那么此之后執(zhí)行的邏輯和橙,就是下拉刷新過程仔燕。


五造垛、Footer

比Header包含的狀態(tài)稍微多一點,但是和header的邏輯是類似的晰搀。
Footer分為Auto和Back兩種狀態(tài)

  • 聯(lián)系:與header的比較五辽,之前處理 ~Header的邏輯現(xiàn)在遷移到了AutoFooter和BackFooter。

  • 區(qū)別:~BackFooter的刷新是通過手動拖拽外恕,并且在tableview的尾部最后進行展示的杆逗。而~AutoFooter的刷新操作,當上滑到最后一個cell時鳞疲,將會自動進行刷新的操作罪郊。

對于footer通常不會去主動調用- beginRefresing該方法。他在設置好相應的初始位置后尚洽,就開始進行手動與UI
進行交互的上拉刷新操作:

~BackFooter:
  1. footer的位置在屏幕的下方(無論是contentsize.height是否大于screensize.height).
  2. 之后的操作就不再贅述排龄,和下拉刷新的流程真的一模一樣。(可以通過源碼進行對照查看)
~AutoFooter:
  1. footer.y = contentsize.height + △ (其中△是一些inset信息).
  2. 之后的刷新也和下拉是一致的內容.

六翎朱、常見的錯誤?:

  • 問題出現(xiàn)點:在上拉刷新的時候橄维,根據(jù)有沒有更多數(shù)據(jù),來更改state拴曲。

  • 代碼:

    if pullup {
        self.endFooterRefreshing(noMoreData: noMoreData)
    } 
    else {
         self.tableView.mj_header.endRefreshing()
    }

    private func endFooterRefreshing(noMoreData: Bool) {
        if noMoreData {
            self.tableView.mj_footer.endRefreshingWithNoMoreData()
        } else {
            self.tableView.mj_footer.resetNoMoreData()
        }
    }
  • 導致結果:當所有數(shù)據(jù)都加載完畢后争舞,重新下拉刷新,然后上拉加載時澈灼,由于footer處于nomoredata狀態(tài)竞川,而無法加載其他頁的數(shù)據(jù)。

  • 分析:
    從pullup代碼來看叁熔,當上拉加載完所有數(shù)據(jù)后委乌,將狀態(tài)更新為了nomoredata狀態(tài)。
    當重新在該頁面進行下拉刷新時荣回,數(shù)據(jù)只加載page == 1 的數(shù)據(jù)遭贸,但是此時footer.state == nomoredata。
    當要進行上拉加載page == 2 的數(shù)據(jù)時心软,由于state == nomoredata壕吹,在源碼中就直接結束了整個刷新的操作.

  • 源碼:
    scrollViewContentOffsetDidChange:

    ~BackFooter:

    // 如果已全部加載,僅設置pullingPercent删铃,然后返回
    if (self.state == MJRefreshStateNoMoreData) {
        self.pullingPercent = pullingPercent;
        return;
    }
    

    ~AutoFooter:

      if (self.state != MJRefreshStateIdle 
          || !self.automaticallyRefresh 
          || self.mj_y == 0) 
        return;
    
  • 解決方案:

      if pullup {
          self.tableView.endFooterRefreshing(noMoreData: noMoreData)
      } else {
          self.tableView.endHeaderRefreshing(noMoreData: noMoreData, count: newcount)
      }
    
    // 給scrollview類進行擴展
    extension UIScrollView {
      
      /// 尾部停止刷新
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的數(shù)據(jù)
      func endFooterRefreshing(noMoreData: Bool) {
          guard mj_footer != nil else { return }
          if noMoreData {
              mj_footer.endRefreshingWithNoMoreData()
          } else {
              footerEndRefreshing(action: nil)
          }
      }
      
      /// 頭部停止刷新
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的數(shù)據(jù)
      /// - count: 返回的結果個數(shù)
      func endHeaderRefreshing(noMoreData: Bool, count: Int) {
          guard mj_header != nil else { return }
          mj_header.endRefreshing()
          updateFooterState(noMoreData: noMoreData, count: count)
      }
      
      /// 根據(jù)首次刷新數(shù)據(jù), 更新footer狀態(tài)
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的數(shù)據(jù)
      /// - count: 返回的結果個數(shù)
      private func updateFooterState(noMoreData: Bool, count: Int) {
          guard mj_footer != nil else { return }
          if noMoreData {
              mj_footer.endRefreshingWithNoMoreData()
              if count == 0 {
                  mj_footer.isHidden = true
              }
          } else {
              mj_footer.isHidden = false
              mj_footer.resetNoMoreData()
          }
      }
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末耳贬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猎唁,更是在濱河造成了極大的恐慌咒劲,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腐魂,居然都是意外死亡慕的,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門挤渔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肮街,“玉大人,你說我怎么就攤上這事判导〖蹈福” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵眼刃,是天一觀的道長绕辖。 經常有香客問我,道長擂红,這世上最難降的妖魔是什么仪际? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮昵骤,結果婚禮上树碱,老公的妹妹穿的比我還像新娘。我一直安慰自己变秦,他們只是感情好成榜,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹦玫,像睡著了一般赎婚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樱溉,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天挣输,我揣著相機與錄音,去河邊找鬼福贞。 笑死撩嚼,一個胖子當著我的面吹牛,可吹牛的內容都是我干的肚医。 我是一名探鬼主播绢馍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肠套!你這毒婦竟也來了?” 一聲冷哼從身側響起猖任,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤你稚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刁赖,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡搁痛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宇弛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸡典。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖枪芒,靈堂內的尸體忽然破棺而出彻况,到底是詐尸還是另有隱情,我是刑警寧澤舅踪,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布纽甘,位于F島的核電站,受9級特大地震影響抽碌,放射性物質發(fā)生泄漏悍赢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一货徙、第九天 我趴在偏房一處隱蔽的房頂上張望左权。 院中可真熱鬧,春花似錦痴颊、人聲如沸涮总。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瀑梗。三九已至,卻和暖如春裳扯,著一層夾襖步出監(jiān)牢的瞬間抛丽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工饰豺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亿鲜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓冤吨,卻偏偏與公主長得像蒿柳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漩蟆,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容

  • 本文轉載自J_Knight 的MJRefresh源碼解析 MJRefresh是李明杰的作品垒探,到現(xiàn)在已經有9800多...
    Detective41閱讀 657評論 0 1
  • 1.前言 MJRefresh 是日常 iOS 開發(fā)中使用頻率比較高的一款下拉刷新/上拉加載更多的第三方控件,平時似...
    RiverSea閱讀 1,386評論 0 10
  • 李明杰老師的代表作之一MJRefresh可以說是棒棒的怠李,很多小伙伴都會在沒有什么特殊要求的情況下使用這個框架圾叼,簡單...
    sunxu_cocoa閱讀 519評論 0 1
  • MJRefresh是流行的下拉刷新控件蛤克,前段時間為了修復一個BUG,讀了它的源碼夷蚊,本文總結一下實現(xiàn)的原理 下拉刷新...
    晚安的你我閱讀 435評論 0 0
  • 下拉刷新01-默認 self.tableView.header = [MJRefreshNormalHeader ...
    Lewis海閱讀 39,476評論 12 14