開發(fā)筆記之打造通用下拉刷新(介紹篇)
開發(fā)筆記之打造通用下拉刷新(細(xì)節(jié)篇)
開發(fā)筆記之打造通用下拉刷新(重難點篇)
本篇不講總體實現(xiàn)思路和每一個細(xì)節(jié),只講一下實現(xiàn)過程中遇到的幾個關(guān)鍵問題,雖然叫重難點渊抄,但也并不是什么高級復(fù)雜的技術(shù)秧荆,只是一些實現(xiàn)過程中遇到的問題并且通過不斷思考和嘗試之后才得到的結(jié)果。
一、在哪里實現(xiàn)事件處理邏輯
我們知道,安卓給我們提供了三個事件處理的方法:onTouchEvent、onInterceptTouchEvent劫拢、dispatchTouchEvent肉津。一般我們自定義控件的時候,事件的攔截有兩種思路(來自《android 開發(fā)藝術(shù)探索》):
- 外部攔截法舱沧,點擊事件先經(jīng)過父容器判斷妹沙,如果父容器需要此事件就攔截,否不攔截熟吏,這種方法比較符合點擊事件的分發(fā)機(jī)制距糖。此方法需要重寫父容器的onInterceptTouchEvent方法,攔截事件后在onTouchEvent方法中寫控件邏輯.
- 內(nèi)部攔截法牵寺,父容器不攔截任何事件悍引,所有的事件都傳遞給子元素,如果子元素需要此事件就消耗掉帽氓,否則就交給父容器去處理趣斤,這種方法和android中的事件分發(fā)機(jī)制不一致,需要配合requestDisallwInterceptTouchEvent方法才能正常工作黎休,需要子元素重寫dispatchTouchEvent方法浓领。
現(xiàn)在來考慮ptrLayout的實現(xiàn),內(nèi)部攔截法對子元素有特定的要求势腮,需要上下配合联贩,不符合我們的要求,因為ptrLayout不知道也不應(yīng)該知道子view的具體實現(xiàn)捎拯。再看外部攔截法泪幌,在onInterceptTouchEvent方法中攔截事件,同樣不符合ptrLayout的要求玄渗,原因如下:
- 我們在acton_down的時候是無法判斷事件是否要攔截或消費(fèi)的座菠,需要通過action_move計算方向才能決定,但是如果沒有view消費(fèi)down藤树,那就不會有后續(xù)的move,那就無從談攔截的事了拓萌。
- 父容器一旦攔截了某個move事件岁钓,從此以后子view再也無法收到任何事件了,無法實現(xiàn)我在細(xì)節(jié)篇提到的下拉與子view滑動的銜接
那么在ptrLayout是如何控制事件呢微王?——所有的邏輯都在dispatchTouchEvent中實現(xiàn)屡限。ptrLayout重寫了dispatchTouchEvent方法,在這個方法中炕倘,如果希望子元素接收到事件钧大,則調(diào)用 super.dispatchTouchEvent(),否則不調(diào)用罩旋。這樣一來就可以靈活地控制事件的傳遞啊央。另外眶诈,為了防止沒有view消費(fèi)down事件從而無法接收后續(xù)的move事件,任何時候都要在dispatchTouchEvent方法的down事件中返回true瓜饥。
二逝撬、子view按下效果的取消
在上一節(jié)提到過,手指按下的時候無法判斷是否要攔截事件乓土,所以action_down是一定會傳到子view宪潮,如果子view是listview或者button等控件,就會出現(xiàn)被按下的效果(比如顏色加深趣苏、5.0的ripple波紋)狡相,如果隨后的move事件被ptrLayout攔截了,那么子view的按下效果是不會消失的食磕,會一直顯示著被按住狀態(tài)尽棕。如圖
那么為什么我們平時用onInterceptTouchEvent攔截事件的時候不會有這樣的問題呢?原因就是如果父容器攔截了事件芬为,那么其子view就會收到一個action_cancel事件萄金,從而讓子view知道事件被攔截了,取消當(dāng)前的效果媚朦。既然知道了原因氧敢,那么解決辦法自然就出來了,那就是在我們攔截的時候询张,自己手動向下發(fā)一個acion_cancel事件孙乖,主要的代碼是這樣的:
public boolean dispatchTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
...
case ACTION_MOVE:
if(需要攔截并通知下層取消事件)
{
//構(gòu)造一個cancel事件,其他參數(shù)與當(dāng)前事件一致
MotionEvent cancelEvent = MotionEvent.obtain(ev.getDownTime(), ev.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, ev.getX(), ev.getY(), ev.getMetaState());
super.dispatchTouchEvent(cancelEvent);
}
}
...
}
這樣一來份氧,子view的問題就可以解決了唯袄。當(dāng)然,我們應(yīng)該控制cancel事件只向下發(fā)一次蜗帜,就是剛開始攔截的那一次恋拷,之后就不用再發(fā)了,以免無謂的方法調(diào)用浪費(fèi)運(yùn)算資源厅缺。
三蔬顾、下拉與子view滑動的銜接
這個點已經(jīng)提到過兩次了(具體效果看細(xì)節(jié)篇),前面的dispatchTouchEvent的重寫就是為了實現(xiàn)這一特性湘捎。首先我們應(yīng)該要清楚诀豁,ptrLayout什么時候該攔截事件,什么時候該傳遞事件窥妇,然后用第一節(jié)說的方法去攔截和傳遞就可以了舷胜。關(guān)于判斷攔截條件這一點,涉及到的條件判斷太多了活翩,這里不一一細(xì)說烹骨,有興趣可以看具體的代碼翻伺,里面有各種注釋,代碼比文字更容易明白展氓。這里只說一種情況穆趴,又是需要手動發(fā)事件才能解決的。文字描述比較乏力遇汞,看圖
子view在Y1處收到down事件到在Y2處收到move事件未妹。從上一節(jié)我們知道,子view在收到down事件后會馬上收到一個cancel事件空入,然后再收到move事件络它,也就是等于子view直接收到move事件的,這時子view可能有兩種處理方法:
- 直接忽略這些Move事件歪赢,因為沒有收到down事件化戳,無法判斷滑動的距離。這是大部分滑動控件的做法埋凯。忽略move事件点楼,就意味著整個滑動沒有銜接起來。
- 處理move事件白对,按上一次的down事件的位置來計算滑動的距離掠廓,但是由于Y2到Y(jié)1是有一定的距離的,子view會在一瞬間滑動Y1-Y2的距離甩恼,雖然滑動銜接起來了蟀瞧,還是畫面沒有銜接起來。
這里解決辦法的思路和第二節(jié)一樣条摸,既然缺一個action_down事件悦污,那么我們就手動發(fā)一個就可以了,發(fā)的時機(jī)就是手指剛到達(dá)Y2的時候钉蒲。代碼大概是這樣的:
public boolean dispatchTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
...
case ACTION_MOVE:
if(不再攔截move事件)
{
if(還沒發(fā)送down事件)
{
//構(gòu)造一個down事件切端,其他參數(shù)與當(dāng)前事件一致
MotionEvent downEvent = MotionEvent.obtain(ev.getDownTime(), ev.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_Down, ev.getX(), ev.getY(), ev.getMetaState());
return super.dispatchTouchEvent(downEvent);
}
}else//已經(jīng)發(fā)送過down事件了,直接向下傳遞move事件
{
return super.dispatchTouchEvent(ev);
}
}
}
...
}
四、橫向滑動處理
當(dāng)我們的子view存在橫向滑動的時候顷啼,比如viewPager帆赢,我們就需要考慮橫向滑動的處理了。首先线梗,并不是任何時候子view都可以橫向滑動的,所以設(shè)置一個mHasHorizontalChild變量怠益,當(dāng)這個功能開啟的時候才會去考慮橫向的處理仪搔。
我們怎么判斷滑動是橫向的還是縱向的呢?這是一個簡單的幾何數(shù)學(xué)題,無非就是計算滑動前后兩個點的連線的斜率蜻牢,k = ΔY / ΔX, 我們可以簡單地認(rèn)為當(dāng)k大于1時為縱向烤咧,小于1時為橫向(當(dāng)然這個1可以是其他值偏陪,可以根據(jù)橫縱向的靈敏度來設(shè)置)。但是煮嫌,考慮到實現(xiàn)的使用體驗笛谦,當(dāng)我們手指開始橫向滑動時就不太可能想要下拉刷新了,意思是昌阿,當(dāng)手指橫向滑動了一段距離饥脑,手指突然向下移動,也不應(yīng)該去觸發(fā)下拉的邏輯懦冰,不然子view向右滑動到一半然后下拉灶轰,頭部顯示出來就會變得十分奇怪,反之亦然刷钢。
這里只是一個細(xì)節(jié)問題笋颤,實現(xiàn)的方法很簡單,開始滑動時判斷當(dāng)前的滑動方向内地,就記住當(dāng)前的判斷結(jié)果伴澄,以后就按照這個結(jié)果來處理事件,直到下一次的滑動阱缓,這里就不再多講非凌。