簡介
點擊事件的事件分發,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生之後,系統需要這個事件傳遞給一個具體的View,而這個傳遞過程就是分發過程。
點擊事件的分發過程由三個重要方法共同完成:
- dispatchTouchEvent 事件分發
- onInterceptTouchEvent 事件攔截
- onTouchEvent 事件響應
方法介紹
public boolean dispatchTouchEvent(MotionEvent ev)
用來進行事件的分發,如果事件能夠傳遞給當前View,那麼此方法一定會被調用,返回結果受到當前View的onTouchEvent和下級View的方法的影響。
- 如果return true,表示點擊事件被消耗掉。如果return false,表示點擊事件沒有被消耗
public boolean onInterceptTouchEvent(MotionEvent ev)
用來判斷是否攔截某個事件。
- 如果return true,表示點擊事件被攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理。如果return false,表示點擊事件未被攔截,View上的事件會被傳遞到子View上,由子View的dispatchTouchEvent來處理。
public boolean onTouchEvent(MotionEvent ev)
用來處理點擊事件,返回結果表示是否消耗當前事件。
- 如果return true,表示點擊事件被接收並消費。如果return false,表示點擊事件未被消費。
三個方法之間的關系
publicbooleandispatchTouchEvent(MotionEventev){
booleanconsume=false;
if(onInterceptTouchEvent(ev)){
consume=onTouchEvent(ev);
}else{
if(hasChild()){
consume = child.dispatchTouchEvent(ev);
}else{
consume = onTouchEvent(ev);
}
}
returnconsume;
}
代碼實踐
界面UI布局
ParentLayout
後面例子,都是在此代碼的基礎上修改的
publicclassParentLayoutextendsLinearLayout{
publicParentLayout(Contextcontext){super(context);}
publicParentLayout(Contextcontext,AttributeSetattrs){super(context,attrs);}
publicParentLayout(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);}
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.dispatchTouchEvent(ev);
}
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.onInterceptTouchEvent(ev);
}
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.onTouchEvent(ev);
}
}
ChildView
後面例子,都是在此代碼的基礎上修改的
publicclassChildViewextendsTextView{
publicChildView(Contextcontext){super(context);}
publicChildView(Contextcontext,AttributeSetattrs){super(context,attrs);}
publicChildView(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);}
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.dispatchTouchEvent(ev);
}
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.onTouchEvent(ev);
}
}
打印結果
點擊ChildView(1處),打印Log
點擊ParentView(2處),打印Log
處理過程解釋
代碼實踐這種情況下,這三個方法,默認都發送false。對於三個方法return的介紹參考前面方法介紹。這裡,我們以點擊ChildView為例子,再次強調點擊實踐的分發流程:
dispatchTouchEvent方法再探
事件分發-false
在ChildView代碼基礎上,將dispatchTouchEvent方法內容改為如下形式
publicclassChildViewextends
TextView{
...
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("false"));
returnfalse;
}
點擊ChildView(1處),打印Log
事件分發-true
在ChildView代碼基礎上,將dispatchTouchEvent方法內容改為如下形式
publicclassChildViewextends
TextView{
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("true"));
returntrue;
}
點擊ChildView(1處),打印Log
觸發兩次Log打印?因為當ChildView的dispatchTouchEvent返回true時,這就表示一系列點擊事件都由ChildView處理,所以,兩次Log分別為手勢的Down和Up事件。而如果返回false時,這就表示一系列點擊事件不由ChildView處理,所以,只打印一次。
onTouchEvent方法再探
事件響應-false
在ChildView代碼基礎上,將onTouchEvent方法內容改為如下形式
publicclassChildViewextends
TextView{
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("false"));
returnfalse;
}
點擊ChildView(1處),打印Log
和默認效果一樣,因為默認也是返回false。
事件響應-true
在ChildView代碼基礎上,將onTouchEvent方法內容改為如下形式
publicclassChildViewextends
TextView{
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("true"));
returntrue;
}
點擊ChildView(1處),打印Log
觸發兩次Log打印?原因同上,這一系列事件都有ChildView出發。
onInterceptTouchEvent方法再探
事件攔截-false
在ParentLayout代碼基礎上,將onInterceptTouchEvent方法內容改為如下形式
publicclassParentLayoutextends
LinearLayout{
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("false"));
returnfalse;
}
點擊ChildView(1處),打印Log
點擊ParentView(2處),打印Log
總結一下上面Log情況?Log結果都和一開始全部默認情況相同。onInterceptTouchEvent為false的情況,就是調用super.onInterceptTouchEvent的情況。onInterceptTouchEvent返回false,表示點擊事件不攔截,向子View傳遞。
事件攔截-true 事件響應-false
在ParentLayout代碼基礎上,將ParentLayout改為如下形式
publicclassParentLayoutextends
LinearLayout{
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.dispatchTouchEvent(ev);
}
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("true"));
returntrue;
}
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("false"));
returnfalse;
}
}
點擊ChildView(1處)或者ParentLayout(2處),打印Log
為什麼點擊ChildView和ParentLayout顯示相同Log?因為MotionEvent一系列事件,已經被onInterceptTouchEvent攔截了,所以MotionEvent一系列事件將不會被傳遞到子View(ChildView)中。
為什麼攔截了點擊事件,但是Log只出現了一次?因為使用onInterceptTouchEvent攔截了點擊事件,只是攔截了點擊事件的傳遞,並沒有處理MotionEvent的一系列事件。更進一步說,只有dispatchTouchEvent方法返回true,才表示MotionEvent的一系列事件被處理。onTouchEvent方法返回true,只是間接的對dispatchTouchEvent產生了影響。
事件攔截-true 事件響應-true
在ParentLayout代碼基礎上,將ParentLayout改為如下形式
publicclassParentLayoutextends
LinearLayout{
@Override
publicbooleandispatchTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("default:false"));
returnsuper.dispatchTouchEvent(ev);
}
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("true"));
returntrue;
}
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("true"));
returntrue;
}
}
點擊ChildView(1處)或者ParentLayout(2處),打印Log
為什麼Log和之前看到的不一樣?對於這次的Log,其實還是包含了點擊事件Down和Up。在這我們用分割線分開,第一部分,出現了dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三個函數;第二部分,因為onInterceptTouchEvent(攔截方法),只被調用一次哦,所以只出現了dispatchTouchEvent, onTouchEvent兩個函數。
View中Listener的優先級
主要探究OnTouchListener和OnClickListener還有onTouchEvent三個方法之間的關系:
界面
ChildView
publicclassChildViewextendsTextView{
publicChildView(Contextcontext){
super(context);
}
publicChildView(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
publicChildView(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);}
@Override
publicbooleanonTouchEvent(MotionEventev){
ABLog.e("DefaultReturn"+String.valueOf("false"));
returnsuper.onTouchEvent(ev);
}
}
MainActivity
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ChildViewview=(ChildView)findViewById(R.id.myChildView);
view.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
ABLog.e("OnClickListener");
}
});
view.setOnTouchListener(newView.OnTouchListener(){
@Override
publicbooleanonTouch(Viewv,MotionEventevent){
ABLog.e("setOnTouchListener");
returnfalse;
}
});
}
}
點擊ChildView(1處),打印Log
前一部分,為Action_Down觸發;後一部分,為Action_Up觸發。
onTouch方法再探
將onTouch方法返回值改為true,表示處理點擊時間的一系列事件,將setOnTouchListener方法改為:
view.setOnTouchListener(newView.OnTouchListener(){
@Override
publicbooleanonTouch(Viewv,MotionEventevent){
ABLog.e("setOnTouchListener");
return true;
}
});
點擊ChildView(1處),打印Log
點擊時間在OnTouch方法中被處理,所以不會傳到onTouchEvent方法中去了。因此更加不會傳到OnClickListener監聽方法中去了。
一個為Action_Down觸發,一個為Action_Up觸發。