編輯:關於Android編程
View的事件分發機制,也稱為View的事件攔截機制,在說事件分發機制之前,需要對MotionEvent對象就行分析,也就是點擊事件,MotionEvent是手指接觸屏幕後所產生的一系列事件,典型的事件類型有如下幾種:
ACTION_DOWN——手指剛接觸屏幕;
ACTION_MOVE——手指在屏幕上移動:
ACTION_UP——手指在屏幕上松開的一瞬間。
點擊屏幕後松開,事件順序 DOWN->UP
點擊屏幕滑動一會再松開,事件順序為DOWN->MOVE->.....MOVE->UP
所謂點擊事件的分發過程,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生了以後,系統需要把這個事件傳遞給一個具體的View,而這個傳遞的過程就是分發過程。分發過程由三個很重要的方法來共同完成;分別是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。
public boolean dispatchTouchEvent(MotionEvent event)
用來進行事件的分發。從這個方法的名稱就能看出來它的含義。如果事件能夠傳遞給當前View,那麼此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法內部調用,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會被再次調用,返回結果表示是否攔截當前事件。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法內部調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。
我們知道View結構是樹形結構,也就是說,View可以放在ViewGroup裡面,通過不同的組合來實現不同的樣式。
現在假設一個情況:
一個View放在ViewGroup1中,這個ViewGroup1又放在另外一個ViewGroup2中,現在來分析點擊事件的分發過程。
布局如上圖所示。
ViewGroup1最外層的ViewGroup ViewGroup2中間的ViewGroup。View最底層的View。
他們代碼都很簡單,都只是重寫了時間攔截核處理的幾個方法。
代碼貼上:
package com.qian; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class ViewGroup1 extends LinearLayout{ private String tag = "ViewGroup1"; @SuppressLint("NewApi") public ViewGroup1(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ViewGroup1(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroup1(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(tag, "onTouchEvent"); return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(tag, "onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(tag, "dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } }
package com.qian; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class ViewGroup2 extends LinearLayout{ private String tag = "ViewGroup2"; @SuppressLint("NewApi") public ViewGroup2(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ViewGroup2(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroup2(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(tag, "onTouchEvent"); return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(tag, "onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(tag, "dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } }
package com.qian; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; public class View1 extends TextView{ private String tag = "View1"; public View1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public View1(Context context, AttributeSet attrs) { super(context, attrs); } public View1(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(tag , "onTouchEvent"); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(tag , "dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } }
布局效果如圖所示:
從上面的代碼可以看出,ViewGroup級別比View的級別要高一點,比View多了方法。onInterceptTouchEvent,這個方法看名字就能猜到是事件攔截機制的核心方法,由於View中已經不能再放子元素了,所以它沒有這個方法。
上面的代碼只是添加了一些Log而已,布局改了一下北京顏色,主要是方便觀察。現在點擊一下中間的View1所在的綠色的區域,打印的Log如下:
從打印的Log可以得出事件分發或者說事件傳遞的順序為,從dispatchTouchEvent和onInterceptTouchEvent方法看:
ViewGroup1——>ViewGroup2——>View1
而事件的處理順序可以從onTouchEvent方法看出:
View1——>ViewGroup2——>ViewGroup1
事件傳遞的返回值表示 true,攔截 false 不攔截繼續往下傳遞 默認為false
事件處理的返回值也類似, true 處理了 不用審核了 false 給上級處理 默認值false
事件傳遞過程,這裡暫時先不管dispatchTouchEvent這個方法,這個方法一般不太會改這個,所以這裡主要關注onInterceptTouchEvent方法,它的返回值就表示是否攔截事件。所以可以可以把上面的整個事件過程整理為下面一張圖:
下面再改一下代碼,加深理解.
在ViewGroup1中修改onInterceptTouchEvent,返回true
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(tag, "onInterceptTouchEvent"); return true; //return super.onInterceptTouchEvent(ev); }
跟我們設想的一樣,ViewGroup1攔截了事件,那麼ViewGroup2和View1就不能收到事件。所以Log中只能看到ViewGroup1的Log。ViewGroup1攔截事件的過程如下圖所示:
同樣,我們修改ViewGroup2的onInterceptTouchEvent,返回true ViewGroup1改回默認的false。打印的Log如下:
可以看到,這次是ViewGroup2攔截了事件。ViewGroup2攔截事件的過程如下圖所示:
這樣對事件的分發,攔截應該就比較清楚了,下面再看看對事件的處理,也就是onTouchEvent方法。這裡最底層的View1,每次處理完任務也就是事件,都需要向上級報告,需要上級的確認,所以事件處理需要返回false。那麼如果返回true,又是怎麼樣的呢?直接看Log打印。
@Override public boolean onTouchEvent(MotionEvent event) { Log.i(tag , "onTouchEvent"); return true; //return super.onTouchEvent(event); }
此時View1處理事件的過程如下:
如果如果ViewGroup的2onTouchEvent方法true。
總結一下,用一段偽代碼
@Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean isConsume = false; if(onInterceptTouchEvent(ev)) { isConsume = onTouchEvent(ev); } else { isConsume = dispatchTouchEvent(ev); } return isConsume; }
順便提一下onTouchListener,當一個View需要處理事件時,如果它設置了onTouchListener,那麼onTouchListener中的onTouch方法會被調用,這時事件如何處理還要看onTouch的返回值,如果onTouch返回false,當前View的onTouch方法會被調用,反之不會被調用,由此可見,onTouchListener的優先級比onTouchEvent優先級要高。
再說onClickListener,在onTouchEvent中,如果當前View設置了onClickListener,那麼他的onClick方法會被調用,前提是onTouchEvent被調用,平時經常用得onClickListener,優先級最低,處於事件傳遞的尾端。
當一個點擊事件產生後,它的傳遞過程遵循如下順序:
Activity——>Window——>View
即事件總是先傳遞給Activity,Activity再傳遞給Window,最後Window再傳遞給頂級View。頂級View接受事件後,就會按照事件分發機制去分發事件。考慮一種情況,如果一個View的onTouchEvent返回false,那麼它的父容器的onTouchEvent方法就會被調用,一次類推,如果所有元素的onTouchEvent都返回false,那麼最終這個事件會傳遞給Activity去處理。這就是Activity為什麼也有onTouchEvent方法和dispatchTouchEvent方法,但是它沒有onInterceptTouchEvent方法,因為它處在整個事件傳遞的最高層,不需要onInterceptTouchEvent攔截事件這個方法。
總結一些View的分發機制的結論。
1)同一個事件序列是指觸摸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件徐柳以down事件開始,中間含有數量不定的move事件,最終以up事件結束。
2)正常情況下,一個事件序列只能被一個View蘭家且消耗,原因參考3),因為一旦一個元素攔截了某些事件,那麼同一個事件序列的所有事件都會交給它處理,但不是絕對的,可以強制。
3)某個View一旦絕對攔截,那麼這個一個事件序列都只能由它來處理,並且它的onInterceptTouchEvent方法不會被再次調用。
4)某個View一旦開始處理事件,如果不消耗down,onTouchEvent返回false,那麼這個一個事件序列都不會交給它來處理,並且事件將重新交給它的父容器去處理。
5)如果所有的View都不處理事件,這個事件最終會小時。
6)ViewGroup默認不攔截任何事件。
7)View沒有onInterceptTouchEvent方法,也就是不攔截事件,只要有時間傳遞給它,它的onTouchEvent方法就會被調用。
8)View的onTouchEvent默認都會消耗事件,也就是onTouchEvent方法返回true。除非它是不可點擊的(clickable和longclickable同時為false)。View的longclickable默認為false,clickable要看情況,比如Button默認是true,也就是Button默認會消耗事件,onTouchEvent方法返回true。但是TextView不是不可點擊的,onTouchEvent方法返回false
這裡說明一下為什麼最上面View1采用繼承TextView,因為TextView不可點擊,方便演示。
9)View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態的。主要clickable和longclickable有一個為true,那麼它的onTouchEvent方法就會返回true。
10)onClick會發生的前提是當前View是可點擊的,並且它收到了down和up事件。
11)事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然後再由父元素傳遞給子View,但是子元素可以通過requestDisallowTouchEvent方法干擾父元素的分發過程,但是down事件除外。
Google在2015的IO大會上,給我們帶來了更加詳細的Material Design設計規范,同時,也給我們帶來了全新的Android Design Support
前段時間公司項目中有用到Bezier曲線的知識,在熟悉Bezier曲線原理和實現方式後,我突發奇想在Android客戶端實現Bezier曲線的構建動畫,於是有了Bezie
在之前Google的系統還沒有更新的時候可以通過修改GoAgent的proxy.ini來登陸美國Play。現在介Google Play美國登錄的辦法,但是G
Volley框架的學習馬上就可以“殺青”了,哈哈,有木有點小激動呢,之所以將這個框架分成三篇來寫,而且涉及的細節比較多,是因為考慮到後面還要學習