當(dāng)手指觸摸到屏幕時(shí),系統(tǒng)就會(huì)調(diào)用相應(yīng)View的onTouchEvent鳍烁,并傳入一系列的action叨襟。
onTouchEvent的傳遞
當(dāng)有多個(gè)層級(jí)的View時(shí),在父層級(jí)允許的情況下幔荒,這個(gè)action會(huì)一直向下傳遞直到遇到最深層的View恩敌。所以touch事件最先調(diào)用的是最底層View的onTouchEvent显设,如果View的onTouchEvent接收到某個(gè)touch action并作了相應(yīng)處理,最后有兩種返回方式return true和return false;return true會(huì)告訴系統(tǒng)當(dāng)前的View需要處理這次的touch事件迂烁,以后的系統(tǒng)發(fā)出的ACTION_MOVE,ACTION_UP還是需要繼續(xù)監(jiān)聽并接收的领曼,而且這次的action已經(jīng)被處理掉了缆巧,父層的View是不可能觸發(fā)onTouchEvent了。所以每一個(gè)action最多只能有一個(gè)onTouchEvent接口返回true莉炉。如果return false钓账,便會(huì)通知系統(tǒng),當(dāng)前View不關(guān)心這一次的touch事件絮宁,此時(shí)這個(gè)action會(huì)傳向父級(jí)梆暮,調(diào)用父級(jí)View的onTouchEvent。但是這一次的touch事件之后發(fā)出的任何action绍昂,該View都不會(huì)再接受啦粹,onTouchEvent在這一次的touch事件中再也不會(huì)觸發(fā),也就是說一旦View返回false窘游,那么之后的ACTION_MOVE唠椭,ACTION_UP等ACTION就不會(huì)在傳入這個(gè)View,但是下一次touch事件的action還是會(huì)傳進(jìn)來的忍饰。
父層的onInterceptTouchEvent截獲
前面說了底層的View能夠接收到這次的事件有一個(gè)前提條件:在父層級(jí)允許的情況下贪嫂。假設(shè)不改變父層級(jí)的dispatch方法,在系統(tǒng)調(diào)用底層onTouchEvent之前會(huì)先調(diào)用父View的onInterceptTouchEvent方法判斷艾蓝,父層View是不是要截獲本次touch事件之后的action力崇。如果onInterceptTouchEvent返回了true斗塘,那么本次touch事件之后的所有action都不會(huì)再向深層的View傳遞,統(tǒng)統(tǒng)都會(huì)傳給父層View的onTouchEvent亮靴,就是說父層已經(jīng)截獲了這次touch事件逛拱,之后的action也不必詢問onInterceptTouchEvent,在這次的touch事件之后發(fā)出的action時(shí)onInterceptTouchEvent不會(huì)再次調(diào)用台猴,直到下一次touch事件的來臨朽合。如果onInterceptTouchEvent返回false,那么本次action將發(fā)送給更深層的View饱狂,并且之后的每一次action都會(huì)詢問父層的onInterceptTouchEvent需不需要截獲本次touch事件曹步。只有ViewGroup才有onInterceptTouchEvent方法,因?yàn)橐粋€(gè)普通的View肯定是位于最深層的View休讳,touch事件能夠傳到這里已經(jīng)是最后一站了讲婚,肯定會(huì)調(diào)用View的onTouchEvent。
底層View的getParent().requestDisallowInterceptTouchEvent(true)
對(duì)于底層的View來說俊柔,有一種方法可以阻止父層的View截獲touch事件筹麸,就是調(diào)用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底層View收到touch的action后調(diào)用這個(gè)方法那么父層View就不會(huì)再調(diào)用onInterceptTouchEvent了雏婶,也無法截獲以后的action(如果父層ViewGroup和最底層View需要截獲不同焦點(diǎn)物赶,或不同手勢(shì)的touch,不能使用這個(gè)寫死)留晚。
//通知父層ViewGroup酵紫,你不能截獲
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
//也可以寫成這樣,當(dāng)用戶按下的時(shí)候错维,我們告訴父組件奖地,不要攔截我的事件(這個(gè)時(shí)候子組件是可以正常響應(yīng)事件的),拿起之后就會(huì)告訴父組件可以阻止赋焕。
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
最近做了一個(gè)看起來不錯(cuò),但很不人性化的界面設(shè)計(jì)隆判。為了做一個(gè)類似QQ側(cè)滑菜單犬庇,自定義一個(gè)HorizontalScrollView,左側(cè)是菜單蜜氨,右側(cè)內(nèi)容區(qū)域是用4個(gè)Fragment組成的流式布局(可以點(diǎn)擊底部Tab切換Fragment)械筛。其中第一個(gè)Fragment是一個(gè)ScrollView,從上到下包含ViewPager飒炎,GridView,ListView笆豁。所以就造成了左右滑里面嵌套上下滑郎汪,上下滑里面又嵌套1個(gè)左右滑赤赊,和2個(gè)上下滑。
解決方案(自定義View煞赢,復(fù)寫方法):
1.最外層的HorizontalScrollView需要自定義抛计,主要是實(shí)現(xiàn)抽屜效果(不需要寫特別的事件處理)
2.右側(cè)區(qū)域Fragment的布局,最外層的ScrollView直接使用照筑,不需要自定義吹截。
3.ViewPager是重點(diǎn),需要自定義凝危,復(fù)寫其父類ViewGroup的dispatchTouchEvent方法波俄。如果不復(fù)寫,ViewPager將無法獲得touch事件蛾默。
public class ShowViewPager extends ViewPager {
public ShowViewPager(Context context) {
super(context);
}
public ShowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
//簡單但重要一步懦铺,ViewPager獲得touch焦點(diǎn)時(shí)候,阻止父層ScorllView及祖父層SlidingMenu的攔截
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean ret = super.dispatchTouchEvent(ev);
if(ret)
{
requestDisallowInterceptTouchEvent(true);
}
return ret;
}
}
4.GridView和ListView需要自定義并復(fù)寫onMeasure方法支鸡。取消其滑動(dòng)屬性冬念,如果不復(fù)寫,GridView和ListView將只顯示一行牧挣。
//自定義GridView急前,重寫onMeasure方法,使其失去滑動(dòng)屬性瀑构,這樣才能嵌套在同樣具有滑動(dòng)屬性的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í)行順序?yàn)椋?/p>
首先觸發(fā)ACTIVITY的dispatchTouchEvent
然后觸發(fā)ACTIVITY的onUserInteraction
![搜索](http://img.baidu.com/img/iknow/qb/select-search.png)然后觸發(fā)LAYOUT的dispatchTouchEvent
然后觸發(fā)LAYOUT的onInterceptTouchEvent
這就解釋了重寫ViewGroup時(shí)必須調(diào)用super.dispatchTouchEvent();
(1)dispatchTouchEvent:
此方法一般用于初步處理事件检碗,因?yàn)閯?dòng)作是由此分發(fā)据块,所以通常會(huì)調(diào)用
super.dispatchTouchEvent。這樣就會(huì)繼續(xù)調(diào)用onInterceptTouchEvent折剃,再由onInterceptTouchEvent決定事件流向另假。
(2)onInterceptTouchEvent:
若返回值為True事件會(huì)傳遞到自己的onTouchEvent();
若返回值為False傳遞到下一個(gè)view的dispatchTouchEvent()怕犁;
(3)onTouchEvent():
若返回值為True边篮,事件由自己處理消耗,后續(xù)動(dòng)作序列讓其處理奏甫;
若返回值為False戈轿,自己不消耗事件了,向上返回讓其他的父view的onTouchEvent接受處理阵子;