開(kāi)發(fā)筆記之打造通用下拉刷新(介紹篇)
開(kāi)發(fā)筆記之打造通用下拉刷新(細(xì)節(jié)篇)
開(kāi)發(fā)筆記之打造通用下拉刷新(重難點(diǎn)篇)
在開(kāi)始寫(xiě)代碼前,我們要明白它的基本需求是什么,既然是打造通用的下拉刷新,那就要看看平時(shí)常見(jiàn)的刷新是怎樣的。從大方面說(shuō)(當(dāng)然不包括所有的情況,這里只是列舉最常見(jiàn)的幾種)。
- 按位置變化分類(lèi):固定頭部、固定內(nèi)容、刷新時(shí)隱藏頭部
- 按觸發(fā)條件分類(lèi):下拉觸發(fā)和釋放觸發(fā)
但是從一些細(xì)節(jié)的體驗(yàn)上,其實(shí)有很多方面可以考慮。比如,刷新時(shí)是否可以拖動(dòng)、刷新完成是否強(qiáng)制上升、還有手指移動(dòng)的連貫性和動(dòng)畫(huà)與滑動(dòng)的沖突處理等。而代碼里的各種布爾變量和條件判斷就是為了處理這些細(xì)節(jié)
開(kāi)始前先講清兩個(gè)概念
- 觸發(fā)線,在下拉刷新模式下,刷新觸發(fā)時(shí)頭部的高度,ThresholdHeight
- 刷新線,松開(kāi)手后的正在刷新高度,RefreshingHeight
刷新時(shí)是否可以拖動(dòng)
我發(fā)現(xiàn),絕大部分的應(yīng)用在刷新時(shí)都是繼續(xù)可以拖動(dòng)頭部的, 但是也有例外,比如安卓自帶的SwipeRefreshLayout,在PullToRefreshLayout(下簡(jiǎn)稱Prtlayout)中對(duì)應(yīng)的是pinContent模式。既然要打造通用下拉刷新,那么就要考慮這兩種情況。
- 刷新時(shí)不可以拖動(dòng)
這意味著,開(kāi)始刷新后,ptrLayout就不再處理任何觸摸事件,而是直接交給下層去處理,直到刷新完成頭部完全隱藏,實(shí)際上SwipeRefreshLayout也是這么做的,它的刷新模式為釋放刷新。但是當(dāng)刷新模式為下拉刷新時(shí)(達(dá)到觸發(fā)線立即刷新),會(huì)出現(xiàn)小問(wèn)題:下拉剛達(dá)到觸發(fā)線,立即開(kāi)始刷新,如果ptrLayout從此不再處理任何事件,那頭部將一直停在觸發(fā)線刷新而不是在刷新線刷新,這里就又多出了兩個(gè)選擇:
1.開(kāi)始刷新后強(qiáng)制回到刷新線;
2.ptrLayout并不是不處理任何事件,而是要處理action_up事件,在松開(kāi)手后判斷是否要回到刷新線。
在ptrLayout里兩種方案都有實(shí)現(xiàn),這也是為什么會(huì)有mCanScrollWhenRefreshing 和mUpToRefredshingImmediately這兩個(gè)布爾成員變量和對(duì)應(yīng)的接口


- 刷新時(shí)可以拖動(dòng)
這通常是大部分的應(yīng)用的做法。刷新時(shí)可以拖動(dòng),意味著我們時(shí)時(shí)都要監(jiān)控著MotionEvent,在拖動(dòng)的時(shí)候?qū)⑵鋽r截。具體表現(xiàn)為:頭部處于正在刷新?tīng)顟B(tài)時(shí),頭部依然會(huì)隨著手指下拉而向下移動(dòng),(關(guān)鍵點(diǎn))這時(shí)候頭部會(huì)再次超過(guò)刷新線,如果當(dāng)前是下拉刷新的話,我們就不能重復(fù)觸發(fā)刷新,所以就會(huì)有一個(gè)isRefreshing變量,代表當(dāng)前是否正在刷新,在觸發(fā)下拉刷新時(shí)作判斷。另外,在下拉之后,(關(guān)鍵點(diǎn))松開(kāi)手后Header要回到刷新線,如果在松開(kāi)手之前刷新已經(jīng)完成怎么辦?
我們又面臨兩個(gè)選擇:
1.不等松手,頭部強(qiáng)制升回頂部(我個(gè)人覺(jué)得這是一種不太好的體驗(yàn),但確實(shí)有應(yīng)用是這么做的,比如網(wǎng)易新聞)
2.在松手之后再回到頂部
(這兩種情況ptrLayout都有實(shí)現(xiàn),強(qiáng)制上升對(duì)應(yīng)mForceToTopWhenFinish成員。)
先說(shuō)第一種情況,既然是強(qiáng)制,意味著上升動(dòng)畫(huà)執(zhí)行過(guò)程中ptrLayout不再處理觸摸事件,所以我們應(yīng)該在觸摸事件響應(yīng)中判斷如果開(kāi)啟”強(qiáng)制返回“****且****強(qiáng)制上升的動(dòng)畫(huà)正在執(zhí)行,則不攔截事件。

再說(shuō)松手之后再回到頂部的情況,當(dāng)我們松手的時(shí)候,怎么知道要升回到刷新線還是升回頂部呢?自然而然就要有一個(gè)IsFinish的變量去表示當(dāng)前是否已經(jīng)刷新完成,來(lái)決定松手后的上升位置。接著,現(xiàn)在手指松開(kāi)了,header開(kāi)始上升,那么又會(huì)引出一個(gè)問(wèn)題:上升的時(shí)候手指又拖動(dòng)怎么辦?無(wú)非又是兩種情況,****強(qiáng)制與不強(qiáng)制****,因?yàn)橹耙呀?jīng)有強(qiáng)制與不強(qiáng)制的選擇了,所以在ptrLayout實(shí)現(xiàn)中,這里是選擇不強(qiáng)制,意味著****手指再次按下時(shí)上升的動(dòng)畫(huà)要取消****(真是糾結(jié)-_-#)

如果不取消,上升動(dòng)畫(huà)和手指拖動(dòng)同時(shí)進(jìn)行的話,就會(huì)出現(xiàn)畫(huà)面抖動(dòng),是個(gè)糟糕的體驗(yàn)。

下拉事件與子view滑動(dòng)的銜接
這是另外一個(gè)小細(xì)節(jié),標(biāo)題可能比較說(shuō)得比較抽象,看下面兩個(gè)例子就知道了。


這兩個(gè)的區(qū)別并不是十分影響體驗(yàn),不過(guò)實(shí)現(xiàn)總比不實(shí)現(xiàn)強(qiáng),要實(shí)現(xiàn)這種銜接的效果,意味著在一次手指按下、拖動(dòng)再松開(kāi)的動(dòng)作中,父布局需要動(dòng)態(tài)判斷事件的消費(fèi)者并靈活控制事件的傳遞,父布局可能會(huì)攔截部分事件,然后向下傳遞部分事件,再攔截部分事件,由于篇幅原因,具體邏輯留到下一篇再討論。
最后
即使是簡(jiǎn)單的下拉也會(huì)有很多小細(xì)節(jié),為了實(shí)現(xiàn)所謂的通用刷新,我把一些細(xì)節(jié)都寫(xiě)成了可配置的項(xiàng)。當(dāng)然還有更多可以考慮的地方,不過(guò)從技術(shù)上來(lái)說(shuō)都是增加判斷而已,所以就沒(méi)必要扯太多了,下一篇文章(也是最后一篇)將講一下代碼實(shí)現(xiàn)的過(guò)程中遇到的一些問(wèn)題與難點(diǎn)。