iOS 下拉重新整理 MJRefresh 詳解

2021-07-11 07:43:00 字數 4455 閱讀 7815

mjrefresh是流行的下拉重新整理控制項,前段時間為了修復乙個bug,讀了它的原始碼,本文總結一下實現的原理

大部分的下拉重新整理控制項,都是用contentinset實現的。預設情況下,如果乙個uiscrollview的左上角在導航欄的正下方,那麼它的contentinset是64,而contentoffset是-64。繼續下拉的話,contentoffset就會越來越小,如果上滑,contentoffset就會增大,直到左上角達到螢幕的左上角時,contentoffset剛好為0

預設情況下,如果下拉乙個uiscrollview,在鬆手之後,會彈回初始的位置(導航欄下方)。而大部分的下拉重新整理控制項,都是將自己放在uiscrollview的上方,起始y設定成負數,所以平時不會顯示出來,只有下拉的時候才會出現,放開又會彈回去。然後在loading的時候,臨時把contentinset增大,相當於把uiscrollview往下擠,於是下拉重新整理的控制項就會顯示出來,然後重新整理完成之後,再把contentinset改回原來的值,實現回彈的效果

基本上,mjrefresh也是這麼實現的

從建立例項的**開始:

mjrefreshnormalheader *

header

=[mjrefreshnormalheader headerwithrefreshingblock:^];

}];

呼叫的是乙個工廠方法headerwithrefreshingblock,這個方法定義在各種header控制項的基類mjrefreshheader裡:

+ (instancetype)headerwithrefreshingblock:(mjrefreshcomponentrefreshingblock)refreshingblock

然後會呼叫init方法,由於mjrefreshheader裡並沒有定義init方法,而它的基類mjrefreshcomponent裡定義了,所以會進入到基類的初始化方法裡:

- (instancetype)initwithframe:(cgrect)frame

return

self;

}

這裡的關鍵是prepare方法,這個方法是第乙個擴充套件點,具體的header(包括庫提供的原生header,和使用者自定義的header)有哪些屬性,樣式是怎麼樣,都是在這個方法裡實現的。每個子類的prepare方法,都會呼叫父類的prepare方法。所以在擴充套件的時候,公共的屬性寫在父類的prepare方法裡,特有的屬性寫在子類的prepare方法裡。比如,我們看一下mjrefreshstateheader的:

- (void)prepare

總之,呼叫headerwithrefreshingblock方法以後,就得到了乙個uiview的例項,也就是下拉重新整理的控制項。但是現在它還沒有掛到任何superview上,也沒有任何行為

接下來的呼叫:

self

.header

=header;

這是利用了uiscrollview+mjrefresh裡的乙個category,為uiscrollview增加了屬性header和footer。這裡用到了關聯物件的技巧(associatedobject),因為category通常情況下是不能直接新增例項變數的

- (void)setheader:(mjrefreshheader *)header

}

通過上面的**,把header新增到了uiscrollview的subviews裡,並保留了乙個引用。但是這個header的frame還沒有確定,也沒有任何行為

由於上面執行了addsubview,接下來就會進入header的生命週期方法willmovetosuperview,這個方法是在公共的基類mjrefreshcomponent裡實現的。因為這是基礎的行為,所以寫在公共的基類裡,所有的子類都能共享:

- (void)willmovetosuperview:(uiview *)newsuperview

}

這裡關鍵是設定了alwaysbouncevertical,這樣才能確保uiscrollview可以下拉,否則需要處理contentsize才能拉得動,就麻煩了很多。此外這裡令header也持有uiscrollview的引用,後續可以從上面取到各種屬性

然後是新增監聽的方法addobservers,這裡主要是用了kvo的技巧:

- (void)addobservers

- (void)observevalueforkeypath:(nsstring *)keypath ofobject:(id)object change:(nsdictionary *)change context:(void *)context

else

if ([keypath isequaltostring:mjrefreshkeypathcontentsize]) else

if ([keypath isequaltostring:mjrefreshkeypathcontentinset]) else

if ([keypath isequaltostring:mjrefreshkeypathpanstate])

}

這裡偵聽了3個key的變化,uiscrollview的contentoffset和contentsize,以及滑動手勢的狀態。然後在每個value發生變化的時候,呼叫幾個didchange方法。這些didchange方法都是hook,是第二個擴充套件點,實際上都是由子類來實現的

接下來會進入生命週期方法layoutsubviews:

- (void)layoutsubviews

這裡的placesubviews就是header應該怎麼擺,是第三個擴充套件點,把header的origin.y設定成負值,就是在mjrefreshheader的這個方法裡實現的:

- (void)placesubviews

每個子類的placesubviews方法,都應該先呼叫父類的這個方法

通過上述的**,確定了下拉重新整理控制項的位置,以及其中每個subview的位置。並且偵聽了uiscrollview的contentoffset和contentsize變化

下拉會導致contentoffset變化,由於前面已經新增了kvo偵聽,所以會執行scrollviewcontentoffsetdidchange方法:

- (void)scrollviewcontentoffsetdidchange:(nsdictionary *)change

// 跳轉到下乙個控制器時,contentinset可能會變

_scrollvieworiginalinset = self

.scrollview

.contentinset;

// 當前的contentoffset

cgfloat offsety = self

.scrollview

.mj_offsety;

// 頭部控制項剛好出現的offsety

.scrollvieworiginalinset

.top;

// 如果是向上滾動到看不見頭部控制項,直接返回

// 普通 和 即將重新整理 的臨界點

.mj_h;

.mj_h;

if (self

.scrollview

.isdragging) else

if (self

.state == mjrefreshstatepulling && offsety >= normal2pullingoffsety)

} else

if (self

.state == mjrefreshstatepulling) else

if (pullingpercent < 1)

}

這段**比較長,主要是判斷offset變化是否達到了臨界值,以及當前的手勢,切換header的state狀態,然後根據state狀態變化,驅動不同的行為:

- (void)setstate:(mjrefreshstate)state

completion:^(bool finished) ];

} else

if (state == mjrefreshstaterefreshing) completion:^(bool finished) ];

}}

setstate方法是第四個擴充套件點,這裡的mjrefreshcheckstate是個巨集,也呼叫了父類的setstate的方法。下拉的時候臨時增大contentinset,令header保留在螢幕上,然後呼叫callback block;結束之後還原contentinset

iOS學習之下拉重新整理

今天我們來給昨天的demo加上下拉重新整理和上拉載入更多的功能.1.下拉重新整理.在viewdidload中呼叫方法addrefreshcontrol,下拉時可以出現風火輪載入更多的效果.void addrefreshcontrol 響應事件的要根據實際情況,這裡寫成乙個方法,當下拉時,重新向伺服器...

下拉重新整理 WEUI下拉重新整理

最近在做手機版使用到了下拉重新整理和滾動載入,記錄一下實現過程 一 引入檔案12 34 二 頁面布局12 3456 78910 1112 1314 1516 1718 19 下拉重新整理 釋放重新整理 正在重新整理 正在載入 三 js部分12 3456 78910 1112 1314 1516 17...

IOS控制項 Tableview 下拉重新整理,載入資料

egorefreshtableheaderview.m兩個檔案,以及enormego提供的那一套包拖進你的工程裡。二 找到你的 uitableviewcontroller 的 h 檔案,新增相應的 import egorefreshtableheaderview.h inte ce rootview...