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...