Android中的事件分發和處理

azal4944 7年前發布 | 5K 次閱讀 安卓開發 Android開發 移動開發

上次跟大家分享了一下自定義View的一下要點,這次跟大家聊一下View的事件分發及處理,為什么主題都是View,因為作為一名初級應用層Android工程師,跟我打交道最多的莫過于各種各樣的View,只有詳細了解他們各自的習性,才能更好地跟他們溝通交流,做出自己想要的效果。

基礎儲備 View、MotionEvent

我們都能詳細地說出Android的四大組件:Activity,Service,ContentProvider和BoardcastReceiver,但是四大組件之外,我們用到也很多的是什么,其中肯定包括View,View是用戶跟程序溝通的入口,也是程序展現給用戶信息的窗口。關于View,一些基礎屬性還是要了解的,left,top,right,bottom,分別代表了view的左上角和右下角分別相對x軸,y軸的坐標,而且view的getWidth和getHeight的值都是通過這四個值算得,而且在Android3.0中還增加了x,y,translationX和translationY這幾個屬性,便于我們對view的平移操作,x、y代表了當前view左上角的xy坐標,而translationX和translationY代表了view相對它的父容器的偏移量,默認值是0。

MotionEvent表示用戶的觸摸事件,用戶的一次點擊、觸摸或者滑動都會產生一系列的MotionEvent:

  • MotionEvent.ACTION_DOWN 表示用戶的手指剛接觸到屏幕
  • MotionEvent.ACTION_MOVE 表示用戶的手指正在移動
  • MotionEvent.ACTION_UP 表示用戶的手指從屏幕上抬起

所以一次用戶觸摸屏幕可能會產生這些事件:

  • 點擊屏幕然后松開,Down->Up
  • 點擊屏幕,然后滑動一段距離,松開屏幕 ,Down->Move->…->Move->Up

了解了這些基本知識以后,我們就來學習一下具體怎么分發這些事件

ViewGroup 分發-> 攔截 -> 處理

首先說一點,雖然ViewGroup也是繼承View而來,但是因為在事件攔截上,ViewGroup分析起來更加方便理解,所以先說ViewGroup,下面也會簡單介紹一下View的事件處理。

在事件分發的過程中,主要涉及到三個方法:

  • dispatchTouchEvent(MotionEvent event);
  • onInterceptTouchEvent(MotionEvent event);
  • onTouchEvent();

初看這三個方法就有蒙圈,如果這時候在蒙頭鉆進源碼里,就更是糊涂,我在這里借用任玉剛大大的一段偽代碼解釋一下這三者之間的關系:

public boolean dispatchTouchEvent(MotionEvent event) { 
 
        boolean consume = false; 
 
        if (onInterceptTouchEvent(event)) { 
 
            consume = onTouchEvent(event); 
 
        } else { 
 
            consume = child.dispatchTouchEvent(event); 
 
        } 
 
  
 
        return consume; 
 
    }  

  • 從這段偽代碼中,我們可以看出來,在dispatchTouchEvent中,先調用ViewGroup自身的onInterceptTouchEvent方法,判斷自己是否要攔截,如果這時候自己攔截,那就調用自己的onTouchEvent方法,如果onTouchEvent方法返回了True,那么這次的事件就算消耗了,事件傳遞到此為止,如果返回了False,證明這次沒有消耗這次MotionEvent,那么這次的事件就會往上返回,由上一級繼續處理;如果當前ViewGroup的onInterceptTouchEvent返回了False,那就會調用它的子view的dispatchTouchEvent方法,這樣這個事件就傳遞下去了,如果它的子View處理不了,那么還會回來調用ViewGroup的onTouchEvent方法,當然這一點是沒有在這一段偽代碼里體現的,用一段通俗的例子解釋:

領導收到一份任務(有可能是上級給的),自己看了一眼,然后決定好好休息,今天不工作,就把這個任務交給了手下的小王,小王的默認屬性是只要來任務就接,而且就干,能干不能干一樣接,如果這是一個簡單的任務,那么小王就解決了,這個任務也就完成了,不幸的是,這次任務小王沒有解決掉,然后向領導反饋,領導沒辦法,手下沒人能解決,只好自己干了,就開始解決問題,然后解決掉,任務也完成了。

這就是ViewGroup層的事件分發,當然不是這么簡單,這只不過是通過簡單的方式去理解,其實在真實的事件分發中,有很多問題需要注意:

  • 一個完成的事件序列以Down開始,中間可能包含若干個Move,然后以Up結束
  • 一個view一旦攔截一個某個事件,當前事件所在的完整事件序列將都會由這個view去處理,反應在真實的代碼中,就是一旦view攔截了down事件,那么此后的move和up事件都將不調用onInterceptTouchEvent,而直接由它處理,這就也意味著在onInterceptTouchEvent處理事件是不合適的,因為有可能來了事件,卻直接跳過onInterceptTouchEvent方法。這個也意味著,一旦一個ViewGroup沒有攔截ACTION_DOWN,那么這個事件序列的其他Action,它都將收不到,所以在處理ACTION_DOWN的時候,尤其需要謹慎。
  • onTouchEvent中是要判斷MotionEvent的Action,因為一次點擊操作就會調用兩次onTouchEvent方法,一次是ACTION_DOWN,一次是ACTION_UP,如果手滑一下,還會有若干個ACTION_MOVE
  • ViewGroup默認不攔截任何事件,源碼中ViewGroup的onInterceptTouchEvent方法默認返回的是false

整個事件分發,看起來都是由外向內傳遞的,父View將事件傳遞給子View,理論上來看,子View是沒有辦法影響到父View的事件處理的,但是有一個標示位,requestDisallowInterceptTouchEvent方法,通過這個方法 ,子View能夠影響父view的事件處理,這個可以用于解決父view和子view的滑動沖突,具體想了解的可以搜索它的相關用法,這里將不進行展開。

View 只有默默的承受

View不同于ViewGroup的是,View中沒有onInterceptTouchEvent方法,因為View作為事件處理的最后一級,不需要判斷是否要攔截,是一定要攔截,不管能不能處理,都要試一下,所以在View中調用流程是:

dispatchTouchEvent -> onTouchEvent

而且,最后onTouchEvent的返回值默認都是True,也就是說事件傳遞下去一般都會被消耗掉的,只是看中途是否有人攔截,這個時候讀者可能會有疑問:TextView的onTouchEvent的返回值也是True嗎?答案就是:是的,那為什么點在TextView上面還是能觸發它的父視圖的onTouchEvent,理論上不應該是,TextView消耗掉這次的事件,不回傳。理論上確實是這樣,但是因為TextView的clickable和longClickable屬性都是false,當這兩個屬性都為false的時候,是不會消耗事件的,所以TextView不會消耗事件,這也就可以解釋為什么把一個TextView放在一個Button上面,然后點擊TextView還是能觸發Button的點擊事件

在這里可能需要提醒一下大家,算是一個我之前踩到的一個坑,我把一個view的enable狀態設成了false,然后又給它增加了onClickListener,這時候我本以為,它的點擊事件不會被觸發,結果它還是可以被點擊,后來才了解到,view的enable狀態和onTouchEvent是沒有關系的,只有clickable狀態是對onTouchEvent有影響的,還有一點 ,設置 view的enable為false確實也會把view的clickable設成false,但是設置view的onclickListener就又把view的clickable變成了true,所以最后的解決方案就是把那兩行代碼換下先后順序,問題就迎刃而解了。

詳解處理GesutureDetector

費勁千辛萬苦,終于把事件攔截下來了,然后我們需要總得做點什么吧,不然都對不起自己浪費這么多口舌,說到對事件的處理,我們首先想到的就是setOnClickListener,殊不知onClickListener的優先級是最低的,下一節里面會對優先級進行說明,而這里,我們將主要想著如果處理事件,當我們興奮地拿到一連串的事件,但又不知如何下手,甚至于連最簡單的點擊事件都要自己進行一番處理,更別提做成平移、旋轉、縮放這樣的操作,但是官方提供的GestureDetector給我們提供了可能。

官方提供的GestureDetector是一個手勢輔助檢測類,默認能夠檢測多種手勢:

class SimpleGestureListener implements GestureDetector.OnGestureListener { 
 
    @Override 
 
    public boolean onDown(MotionEvent e) { 
 
        return false; 
 
    } 
 
  
 
    @Override 
 
    public void onShowPress(MotionEvent e) { 
 
  
 
    } 
 
  
 
    @Override 
 
    public boolean onSingleTapUp(MotionEvent e) { 
 
        return false; 
 
    } 
 
  
 
    @Override 
 
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 
 
        return false; 
 
    } 
 
  
 
    @Override 
 
    public void onLongPress(MotionEvent e) { 
 
  
 
    } 
 
  
 
    @Override 
 
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 
 
        return false; 
 
    } 
 
}  

通過這個類,我們可以很方便的處理除了單擊和長按之外,還有滑動,雙擊等各種手勢,并對其分別進行處理,如果這些還是不能滿足你的好奇心,那還有一個官方提供的ScaleGestureDetector,從名字就可以判斷出來這是一個檢測縮放手勢的輔助類,而且還有大牛仿照ScaleGestureDetector思路做出了平移以及旋轉的輔助類,然后我們就可以根據這些輔助類,幾乎為所欲為了,下面我寫了一個支持平移,縮放,旋轉的小Demo。

private void init() { 
 
  
 
        scaleGesture = new ScaleGestureDetector(getContext(), new ScaleListener()); 
 
        moveGesture = new MoveGestureDetector(getContext(), new MovingListener()); 
 
        rotateGesture = new RotateGestureDetector(getContext(), new RotateListener()); 
 
  
 
    } 
 
  
 
    @Override 
 
    public boolean onTouchEvent(MotionEvent event) { 
 
  
 
        scaleGesture.onTouchEvent(event); 
 
        moveGesture.onTouchEvent(event); 
 
        rotateGesture.onTouchEvent(event); 
 
  
 
        return true; 
 
    } 
 
  
 
  
 
    private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener { 
 
  
 
        @Override 
 
        public boolean onScale(ScaleGestureDetector detector) { 
 
  
 
            setScaleX(detector.getScaleFactor() * getScaleX()); 
 
            setScaleY(detector.getScaleFactor() * getScaleY()); 
 
  
 
            return true; 
 
        } 
 
  
 
        @Override 
 
        public boolean onScaleBegin(ScaleGestureDetector detector) { 
 
            return true; 
 
        } 
 
  
 
        @Override 
 
        public void onScaleEnd(ScaleGestureDetector detector) { 
 
  
 
        } 
 
    }  

只貼了部分的代碼,而且貌似旋轉好像還有點問題,以后時間再修正,有用到的讀者可以在詳細了解下,完整代碼,我會在文章的最后給出鏈接,同時感謝Android multitouch gesture detectors的作者,提供了這么方便的手勢操作類

onTouchListener OnTouchEvent OnClickListener

我們在知道onTouchEvent之前肯定都知道onClickListener和onTouchListener,而他們都是事件的消費者,onTouchListener是在onTouch方法中生效,而且onTouch要先于onTouchEvent,就是說一旦設置了onTouchListener并且最后onTouch方法返回了True,那onTouchEvent將不會再被執行,而onClickListener和onTouchEvent有些關系,onTouchEvent的默認實現里會調用onClickListener的onClick方法,如果重寫了onTouchEvent,因為onClickListener接受不到ACTION_DOWN和ACTION_UP,那么再設置onClickListener也就不會再生效了,這個時候的單擊或者長按處理只能在onTouchEvent中自己處理。

 

來自:http://mobile.51cto.com/android-534617.htm

 

 本文由用戶 azal4944 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!