android 事件處理機制總結,ScrollView ViewPager ListView GridView嵌套小結

當手指觸摸到屏幕時,系統(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個上下滑。

左側菜單抽屜效果(模仿QQ)

解決方案(自定義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
        ![搜索](http://img.baidu.com/img/iknow/qb/select-search.png)然后觸發(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接受處理;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容