事件分发机制学习

(1)事件分发机制学习

只要触摸了屏幕,就会触发dispatchTouchEvent,并且从布局的底层往上执行。也就是最底层的ViewGroup往最顶层的view的手势事件分发。

ViewdispatchTouchEvent方法

publicboolean dispatchTouchEvent(MotionEvent event){

if(mOnTouchListener=null&&(mViewFlags&ENABLE_MASK)==ENABLED

&&mOnTouchListener.onTouch(this,event))

{

returntrue;

}

returnonTouchEvent(event);

}

mOnTouchListener只要控件设置了setOnTouchListener,它就不为空。第一个条件返回true.

(mViewFlags&ENABLE_MASK)==ENABLED:控件是否可用,一般控件默认为可用,返回true.

控件可以通过javacodesetEnabled(false),或者xml文件中android:enabled=”false”来设置不可用。

一般该条件不用管。默认为true就ok。

MonTouchListener.onTouch(this,event):这里指的是setOnTouchListener(newonTouchListener(){

publicboolean onTouch(View v,MotionEvent event)

{

returnfalse/true;//默认是给false.

}

});

如果这三个条件全部满足,dispatchTouchEvent直接返回true.不会执行returnonTouchEvent(event)这句代码。

自然也就不会执行ViewonTouchEvent(MotionEventevent)方法。

mOnClickListener.onClick(this)方法在ViewonTouchEvent(event)方法中进行判断是否执行的。所以如果没有执行到returnonTouchEvent(event)这句,onClick()是绝对不会执行的。


ViewonTouchEvent(MotionEventevent)比较长,节选处比较重要的几条。

publicboolean onTouchEvent(MotionEvent event){

if(viewFlags&CLICKABLE)==CLICKABLE||(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE))

{

switch(event.getAction()){

caseMotionEvent.ACTION_UP:

performClick();

//performClick()中就执行了mOnClickListener.onClick().前提你得先给组件设置setOnClickListener(...)     

break;

caseMotionEvent.ACTION_DOWN:

break;

caseMotionEvent.ACTION_CANCEL:

break;

caseMotionEvent.ACTION_MOVE:

break

}

returntrue;

}

returnfalse;

}

可以获取的信息是如果View可以点击或者可以长按。就会执行if中的语句,并且返回true(这句非常重要,要是if条件没满足的话,就会返回false)。然后View设置了setOnClickListener,就可以响应ViewonClick()事件了。


所以对于ImageView,如果onTouch()返回false,因为它默认不可点击。所以它只能响应MotionEvent.ACTION_DOWN.如果想让它ACTION_UP,ACTION_MOVE都能响应。有两种方式,要么onTouch()返回true.或者设置它为可点击的。


ViewGroup的相关方法

onInterceptTouchEvent(MotionEventevent)

dispatchTouchEvent(MotionEventevent)重写View的该方法

ViewGrouponTouchEvent(MotionEventevent)就是ViewonTouchEvent(MotionEventevent).


publicboolean onInterceptTouchEvent(MotionEvent event){

returnfalse;

}

该方法默认是返回false的。默认ViewGroup容器是不会拦截手势事件的。

除非你重写returntrue.

Publicboolean dispatchTouchEvent(MotionEvent ev){

if(disallowInterceptTouchEvent||!onInterceptTouchEvent(ev))

//disallowInterceptTouchEvent:是否禁用拦截手势事件这项功能,一般不用管它,默认是false.也就是允许这项功

//

//!onInterceptTouchEvent(ev):它为true的意思是不拦截手势事件。默认也是不拦截。

{

...

if(frame.contais(scrolledIntX,scrolledIntY)){

//这句判断当前遍历的View是否是点击的View

//注意,一旦找到了,并且child.dispatchTouchEvent(ev)返回true,这个遍历也就结束了。

//假如两个button,重叠在一起,在同一个ViewGroup.这意味着即使重叠在一起,也只有一个view执行了dispatchTouchEvent方法。而且是后放的那个。看下面的for循环可知。

//假如一个ImageView放在Button上面,先执行到ImageViewdispatchTouchEvent,可以响应ImageViewsetOnTouchListeneronTouch()方法中的MotionEvent.ACTION_DOWN.如果默认让onTouch()方法返回false,因为ImageView不可点击,ImageViewonTouchEvent也会返回false.最终ImageViewdispatchTouchEvent也返回false.然后ImageView下面的Button可以响应到点击事件和手势事件。

finalint count=mChildrenCount;

for(inti=count-1;i>0;i--){

if(child.dispatchTouchEvent(ev)){

mMotionTarget=child;

returntrue;

}

}

}

}

//如果拦截了手势事件,也就是上面的if条件不满足。或者点击的位置没在子View的范围内,也就是frame.contais(scrolledIntX,scrolledIntY)返回false.这样才有机会执行ViewGroupdispatchTouchEvent()方法(其实也是ViewdispatchTouchEvent)。

...

if(target==null){

returnsuper.dispatchTouchEvent(ev);

//这里可以理解为把ViewdispatchTouchEvent(ev)的那部分代码搬到这里,那么那里面的onTouch(this,ev)中的this正好指的是当前ViewGroup的实例

}


}

(2)事件分发测试,加深理解



  • 对于堆叠在一起的控件,必然同时只能其中一个控件响应onClick

    if(!isClickable()) {

  • Android:setClickable 一定要放在按钮的setOnClickListener事件之后,因为setOnClickListener事件会去重写Viewv,也就是会把setClickable设为true.

    View中的源码如下。

    publicvoidsetOnClickListener(OnClickListener l) {

    setClickable(true);

    }

    getListenerInfo().mOnClickListener= l;

    }

  • 系统Button默认android:clickable=”true”;android:longClickabel=”false”;

  • Buttonclickable,longClickable属性都为false时,Button会一直显示没被按下的样子。

  • View首先调用一次dispatchTouchEvent,而第一次响应的必然是ACTION_DOWN.

  • ViewdispatchTouchEvent返回为true的时候,才回响应后续的ACTION_MOVE,ACTION_UP等事件。比如想让TextView响应ACTION_DOWN,ACTION_MOVE,ACTION_UP.有两种方式,

    方法一是mTextView设置Touch监听的时候,把onTouch的返回值设置为true.(默认是false)。

方法一设置后就不响应onClick事件。

  • 方法二是把mTextViewclickable属性设置为true.可同时响应onTouchonClick.

  • 假设Button设置了手势监听和onClick.在满足ACTION_DOWN的位置在Button范围内,ACTION_UP的位置在Button范围外。手势监听的onTouch中的ACTION_UP会执行,但是onClick并不会执行。只有ACTION_UP的位置也在Button范围内,并且ACTION_MOVE从来没有出去过Button范围外。

总结来说onTouch,DOWN,UP,MOVE都执行的条件是DOWN的位置在控件范围内。

onClick执行的条件是DOWN,UP,MOVE的位置一直都在控件范围内。一旦MOVE除控件范围外,即使UP的位置在控件范围内,onClick也不会执行。

  • (3)GridView,ListView事件分发探索。

    GridView,ListView均继承于AbsListViewAbsListView重写了onTouchEvent方法。

    首先明确GridView是容器。我们自定义的item假设是一个RelativeLayout中放一个Button.

    一般容器是默认不拦截事件的。所以事件会从GridView分发到RelativeLayout然后到Button.

    因为Button默认clickabletrue.导致ButtononTouchEvent返回true,进而导致ButtondispatchTouchEvent返回true.不会执行容器的dispatchTouchEvent.

    而我们想要调用GridViewsetOnItemClickListener这个方法。我们需要让事件分发到GridView.

    我们可以把GridView的每个部分理解成A容器--B容器--C组件。也就是要让事件分发到A容器。

所以我们把Buttonandroid:clickable属性设置为false.默认ButtonlongClickable属性也是false.默认onTouch方法也是返回false.

此时事件从C组件分发到B容器,B容器是我们用的 RelativeLayoutorLinearLayout.不设置监听事件的话,会继续分发到A容器。

我们发现此时GridViewsetOnItemClickListener依然没有生效。原因是因为GridViewListView继承的AbsListView重写了onTouchEvent.

查看源码,可以发现AbsListViewonTouchEvent

publicbooleanonTouchEvent(MotionEvent ev) {

.

caseMotionEvent.ACTION_UP:{

onTouchUp(ev);

break;

.

}


privatevoidonTouchUp(MotionEvent ev) {

.

if(inList && !child.hasFocusable()) {

if(mPerformClick== null){

mPerformClick= newPerformClick();

}

.

if(!mDataChanged && mAdapter.isEnabled(motionPosition)){

mTouchMode= TOUCH_MODE_TAP;

setSelectedPositionInt(mMotionPosition);

layoutChildren();

child.setPressed(true);

positionSelector(mMotionPosition,child);

setPressed(true);


..

mTouchModeReset= newRunnable() {

@Override

publicvoidrun() {

mTouchModeReset= null;

mTouchMode= TOUCH_MODE_REST;

child.setPressed(false);

setPressed(false);

if(!mDataChanged && isAttachedToWindow()) {

performClick.run();

}

}

};

.

}

...}


源码中标示出!child.hasFocusable()要为true才能响应点击事件。也就是B容器,C组件不能获得焦点。

所以想要事件分发到A容器。我们还要把Buttonandroid:focusable属性设置为false.B容器默认focusablefalse.(待确认下,应该是的)

也就是Button中以下属性要确认,clickablefalse,longClickabelfalse,focusablefalse.

我们发现可以响应GridViewsetOnItemClickListener后,但是C组件竟然也有按下和释放的效果了。

明明我们在设置clickablefalse,longClickabelfalse后就应该Button无法点击,一直处于未点击的效果不变的。

原因是源码中有child.setPressed(true)child.setPressed(false).

而且源码中有setPressed(true)setPressed(false).

所以A容器,B容器,C组件(可以是多个,已经验证,但要设置好让事件分发到A容器)都会有按下和释放的效果,如果你有设置。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值