當手指觸摸到屏幕時,系統(tǒng)就會調用相應View的onTouchEvent,并傳入一系列的action。
onTouchEvent的傳遞
當有多個層級的View時,在父層級允許的情況下,這個action會一直向下傳遞直到遇到最深層的View。所以touch事件最先調用的是最底層View的onTouchEvent,如果View的onTouchEvent接收到某個touch action并作了相應處理,最后有兩種返回方式return true和return false;return true會告訴系統(tǒng)當前的View需要處理這次的touch事件,以后的系統(tǒng)發(fā)出的ACTION_MOVE,ACTION_UP還是需要繼續(xù)監(jiān)聽并接收的,而且這次的action已經被處理掉了,父層的View是不可能觸發(fā)onTouchEvent了。所以每一個action最多只能有一個onTouchEvent接口返回true。如果return false,便會通知系統(tǒng),當前View不關心這一次的touch事件,此時這個action會傳向父級,調用父級View的onTouchEvent。但是這一次的touch事件之后發(fā)出的任何action,該View都不會再接受,onTouchEvent在這一次的touch事件中再也不會觸發(fā),也就是說一旦View返回false,那么之后的ACTION_MOVE,ACTION_UP等ACTION就不會在傳入這個View,但是下一次touch事件的action還是會傳進來的。
父層的onInterceptTouchEvent截獲
前面說了底層的View能夠接收到這次的事件有一個前提條件:在父層級允許的情況下。假設不改變父層級的dispatch方法,在系統(tǒng)調用底層onTouchEvent之前會先調用父View的onInterceptTouchEvent方法判斷,父層View是不是要截獲本次touch事件之后的action。如果onInterceptTouchEvent返回了true,那么本次touch事件之后的所有action都不會再向深層的View傳遞,統(tǒng)統(tǒng)都會傳給父層View的onTouchEvent,就是說父層已經截獲了這次touch事件,之后的action也不必詢問onInterceptTouchEvent,在這次的touch事件之后發(fā)出的action時onInterceptTouchEvent不會再次調用,直到下一次touch事件的來臨。如果onInterceptTouchEvent返回false,那么本次action將發(fā)送給更深層的View,并且之后的每一次action都會詢問父層的onInterceptTouchEvent需不需要截獲本次touch事件。只有ViewGroup才有onInterceptTouchEvent方法,因為一個普通的View肯定是位于最深層的View,touch事件能夠傳到這里已經是最后一站了,肯定會調用View的onTouchEvent。
底層View的getParent().requestDisallowInterceptTouchEvent(true)
對于底層的View來說,有一種方法可以阻止父層的View截獲touch事件,就是調用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底層View收到touch的action后調用這個方法那么父層View就不會再調用onInterceptTouchEvent了,也無法截獲以后的action(如果父層ViewGroup和最底層View需要截獲不同焦點,或不同手勢的touch,不能使用這個寫死)。
//通知父層ViewGroup,你不能截獲
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
//也可以寫成這樣,當用戶按下的時候,我們告訴父組件,不要攔截我的事件(這個時候子組件是可以正常響應事件的),拿起之后就會告訴父組件可以阻止。
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
pager.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
pager.requestDisallowInterceptTouchEvent(false);
break;
}
}
HorizontalScrollView嵌套ScorllView,再嵌套ViewPager
最近做了一個看起來不錯,但很不人性化的界面設計。為了做一個類似QQ側滑菜單,自定義一個HorizontalScrollView,左側是菜單,右側內容區(qū)域是用4個Fragment組成的流式布局(可以點擊底部Tab切換Fragment)。其中第一個Fragment是一個ScrollView,從上到下包含ViewPager,GridView,ListView。所以就造成了左右滑里面嵌套上下滑,上下滑里面又嵌套1個左右滑,和2個上下滑。

解決方案(自定義View,復寫方法):
1.最外層的HorizontalScrollView需要自定義,主要是實現(xiàn)抽屜效果(不需要寫特別的事件處理)
2.右側區(qū)域Fragment的布局,最外層的ScrollView直接使用,不需要自定義。
3.ViewPager是重點,需要自定義,復寫其父類ViewGroup的dispatchTouchEvent方法。如果不復寫,ViewPager將無法獲得touch事件。
public class ShowViewPager extends ViewPager {
public ShowViewPager(Context context) {
super(context);
}
public ShowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
//簡單但重要一步,ViewPager獲得touch焦點時候,阻止父層ScorllView及祖父層SlidingMenu的攔截
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean ret = super.dispatchTouchEvent(ev);
if(ret)
{
requestDisallowInterceptTouchEvent(true);
}
return ret;
}
}
4.GridView和ListView需要自定義并復寫onMeasure方法。取消其滑動屬性,如果不復寫,GridView和ListView將只顯示一行。
//自定義GridView,重寫onMeasure方法,使其失去滑動屬性,這樣才能嵌套在同樣具有滑動屬性的ScrollView中了。
public class StaticGridView extends GridView {
public StaticGridView(Context context) {
super(context);
}
public StaticGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StaticGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
dispatchTouchEvent的執(zhí)行順序為:
首先觸發(fā)ACTIVITY的dispatchTouchEvent
然后觸發(fā)ACTIVITY的onUserInteraction
然后觸發(fā)LAYOUT的dispatchTouchEvent
然后觸發(fā)LAYOUT的onInterceptTouchEvent
這就解釋了重寫ViewGroup時必須調用super.dispatchTouchEvent();
(1)dispatchTouchEvent:
此方法一般用于初步處理事件,因為動作是由此分發(fā),所以通常會調用
super.dispatchTouchEvent。這樣就會繼續(xù)調用onInterceptTouchEvent,再由onInterceptTouchEvent決定事件流向。
(2)onInterceptTouchEvent:
若返回值為True事件會傳遞到自己的onTouchEvent();
若返回值為False傳遞到下一個view的dispatchTouchEvent();
(3)onTouchEvent():
若返回值為True,事件由自己處理消耗,后續(xù)動作序列讓其處理;
若返回值為False,自己不消耗事件了,向上返回讓其他的父view的onTouchEvent接受處理;