讲一讲Android中View的事件分发机制

学习《Android开发艺术探索》-View事件分发机制总结

点击事件的分发过程由三个很重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEven和onTouchEvent;

* ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
* View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

1.dispatchTouchEvent 方法

public boolean dispatchTouchEvent(MotionEvent ev)

用于进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受到当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。

  • return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
  • return false,事件分发分为两种情况:
    • 1.如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
    • 2.如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
  • return 系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。

2.onInterceptTouchEven 方法

public boolean onInterceptTouchEvent(MotionEvent ev) 

用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。

  • return true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
  • return false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
  • return super.onInterceptTouchEvent(ev),ViewGroup里的onInterceptTouchEvent默认返回值是false,这样touch事件会传递到View控件;

3.onTouchEvent 方法

public boolean onTouchEvent(MotionEvent ev)

同来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件。

  • 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
  • 如果返回了 true 则会接收并消费该事件。
  • 如果返回 super.onTouchEvent(ev) ViewGroup里的onTouchEvent默认返回值是false ,View里的onTouchEvent默认返回值是true。

4.三者关系可以如下伪代码表示:(是伪代码、伪代码、伪代码)

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

5.View(不包含ViewGroup)的点击事件处理过程:

	当一个View需要处理事件时,要先判断它是否设置了OnTouchListener。因为如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。
	这时候事件如何处理要看onTouch的返回值
	  - 如果返回 false ,那么当前View的onTouchEvent方法会被调用。
	  - 如果返回 true,那么View的onTouchEvent方法将不会被调用。

View中优先级:OnTouchListener>onTouchEvent >OnClickListener。

当一个事件产生后,他的传递过程遵循如下顺序:Actovity->Window->View,即事件总是先传递给Activity,Activity在传递给Window,最后Window传递给顶级View。顶级View接收到事件后,就会按照事件分发机制去分发事件。

如果一个View的onTouchEvennt返回false,那么它的父容器的onTouchEvennt将会被调用,依此类推。当所有的元素都不处理该事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvennt会被调用。

子View可通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程,但是ACTION_DOWN是除外;
如在Listview中解决ScrollView滑动冲突问题,可在Listview中加入如下代码:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) { 
    //通知父层ViewGroup不截获  
    getParent().requestDisallowInterceptTouchEvent(true);  
    return super.dispatchTouchEvent(ev);    
} 
//或者
 mListView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            //当用户滑动的时候,我们告诉父组件,不要拦截我的事件(这个时候子组件是可以正常响应事件的),拿起之后就会告诉父组件可以阻止。
            //ACTION_DOWN 的时候requestDisallowInterceptTouchEvent会被重置
                switch (event.getAction()) {
                    case MotionEvent.ACTION_MOVE:                     
                        v.getParent().requestDisallowInterceptTouchEvent(true);
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:  
                         v.getParent().requestDisallowInterceptTouchEvent(false);
                        break;
                }
                return false;
            }
        }); 
 }

6.总结

  1. 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这过程中所产生的一系列事件,这个事件序列以down事件开始,中间含数量不定的move事件,最终以up事件结束。

  2. 正常情况下,一个事件序列只能被一个View拦截且消耗。因为一旦一个元素拦截了此类事件,那么同一个事件序列内的所以事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

  3. 某个View一决定拦截,那么这个事件序列都会由它来处理(如果事件序列能够传递给它的话),并且它的onIntercetTouchEvent不会再被调用。

    就是说:当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他事件都直接交给它来处理,因此就不用再调用这个View的onIntercetTouchEvent方法去询问是否要拦截了。 
    
  4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent 返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent 会被调用。

    意思是事件一旦交给一个View处理,那么它必须消耗掉,否则同一事件序列中剩下的事件就不会再交给它来处理。就好比,领导有件事交给你去处理,如果你没处理好,那么短期内领导就不敢再把事情交给你处理了。
    
  5. 如果View不消耗ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent 不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件将会传递给Activity处理。

  6. ViewGroup默认不拦截任何事件。

  7. View没有onInterceptTouchEvent方法,一旦点击事件传递给它,那么它的onTouchEvent方法会被调用。

  8. View的onTouchEvent方法默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认为false,clickable属性要分情况。如:Button为true。TextView为false。

  9. View的enable属性不影响onTouchEvent的默认返回值。就算View是disable状态,只要clickable和longClickable有一个为true,那么它的onTouchEvent就返回true。

  10. onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。

  11. 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,在子View中可以通过requestDisallowInterceptTouchEvent方法干预父元素的事件分发过程,但是ACTION_DOWN除外。

发布了27 篇原创文章 · 获赞 187 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/Mr_wzc/article/details/52865071