事件分发机制

参考出处:http://blog.csdn.net/guolin_blog/article/details/9097463
 
1.View事件分发
demo code:
  1. btn =(Button) findViewById(R.id.send);
    btn.setOnClickListener(newOnClickListener(){
        @Override
         publicvoid onClick(View v){
         Log.d("TAG","onClick execute");
        }
    });
    btn.setOnTouchListener(newOnTouchListener(){
        @Override
        publicboolean onTouch(View v,MotionEvent event){
        Log.d("TAG","onTouch execute, action "+ event.getAction());
        returnfalse;
        }
    });

任何控件都会调用到dispatchTouchEvent事件分发,本来没有这个方法就往父类找。

  1. publicboolean dispatchTouchEvent(MotionEvent event){
    if(mOnTouchListener !=null&&(mViewFlags & ENABLED_MASK)== ENABLED &&
       mOnTouchListener.onTouch(this, event)){
        return true;
    }
     return onTouchEvent(event);
    }
mOntouchListener赋值,只要给控件注册事件就赋值。
  1. publicvoid setOnTouchListener(OnTouchListener l){
       mOnTouchListener = l;
    }
mViewFlags & ENABLED_MASK == ENABLED:按钮默认是enableaa。
mOnTouchListener.onTouch:回调控件注册事件的onTouch方法,如果控件的ouTouch返回false,就执行onTouchEvent(event)
onTouch返回true,则dispatchTouchEvent也返回true,则onClick就不会执行,因为onClick是在onTouchEvent中的。
onTouchEvent的源码中的performClick方法:
  1. publicboolean performClick(){
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if(mOnClickListener !=null){
       playSoundEffect(SoundEffectConstants.CLICK);
       mOnClickListener.onClick(this);
       returntrue;
    }
       returnfalse;
    }
只要mOnclickListener不为空就会去执行onClick方法。
mOnclickListener赋值在哪里?
  1. publicvoid setOnClickListener(OnClickListener l){
    if(!isClickable()){
       setClickable(true);
       }
       mOnClickListener = l;
    }
touch事件层级传递:
如果给一个控件注册了touch事件,每次触发的时候都会执行ACTION_DOWN/ACTION_MOVE/ACTION_UP事件。
事件分发时,只有前一个action返回true才会触发后一个action(即触发onTouchEvent)。
对于button和imageview这两类不同的控件:
button:本身可以点击,onTouch不管怎么设置,onTouchEvent都会返回true。
imageview:本身不可以点击,onTouch设置返回false,onTouchEvent也返回false,ACTION_DOWN执行后不会触发ACTION_UP
 
onTouch和onTouchEvent区别:
onTouch优先于onTouchEvent执行,onTouch中通过返回true将事件消费掉,onTouchEvent将不会执行。
onTouch执行需满足:mOntouchListener不能为空,点击事件必须为enable,如果控件是非enable的,
如果设置ouTouch永远得不到执行,如果监听Touch事件只能在控件中重写onTouchEvent方法。
 
2.ViewGroup事件分发
Demo code:
自定义viewGroup:MyLayout
  1. publicclassMyLayoutextendsLinearLayout{
       publicMyLayout(Context context,AttributeSet attrs){
       super(context, attrs);
    }
       @Override
    publicboolean onInterceptTouchEvent(MotionEvent ev){
       return true;
       }
    }
MainActivity:
  1. myLayout =(LinearLayout) findViewById(R.id.my_layout);
    button1 =(Button) findViewById(R.id.button1);
    button2 =(Button) findViewById(R.id.button2);
    myLayout.setOnTouchListener(newOnTouchListener(){
        @Override
        publicboolean onTouch(View v,MotionEvent event){
          Log.d("TAG","myLayout on touch");
          return false;
        }
    });
    button1.setOnClickListener(newOnClickListener(){
        @Override
         publicvoid onClick(View v){
           Log.d("TAG","You clicked button1");
         }
    });
    button2.setOnClickListener(newOnClickListener(){
         @Override
         publicvoid onClick(View v){
         Log.d("TAG","You clicked button2");
       }
    });
touch事件的传递是先传递到viewGroup再传递到view的。触摸了任何控件,就一定会调用该控件的dispatchTouchEvent方法。当你点击了某个控件
,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。
ViewGroup的dispatchTouchEvent源码中:
  1. if(disallowIntercept ||!onInterceptTouchEvent(ev)){
    ev.setAction(MotionEvent.ACTION_DOWN);
    finalint scrolledXInt =(int) scrolledXFloat;
    finalint scrolledYInt =(int) scrolledYFloat;
    finalView[] children = mChildren;
    finalint count = mChildrenCount;
disallowIntercept是是否禁用事件拦截功能,默认flase,requestDisallowInterceptTouchEvent方法可修改。
我们在自定义的ViewGroup的onInterceptTouchEvent返回true。那么这段代码就默认flase,则不执行。
接下来就会执行
  1. if(target ==null){
    ev.setLocation(xf, yf);
    if((mPrivateFlags & CANCEL_NEXT_UP_EVENT)!=0){
        ev.setAction(MotionEvent.ACTION_CANCEL);
        mPrivateFlags &=~CANCEL_NEXT_UP_EVENT;
    }
      return super.dispatchTouchEvent(ev);
    }
会调用MyLayout父类即View的方法,MyLayout注册的onTouchEvent会执行。
否则:
  1. child.mPrivateFlags &=~CANCEL_NEXT_UP_EVENT;
       if(child.dispatchTouchEvent(ev)){
        mMotionTarget = child;
        returntrue;
    }
进入到子view的代码中,返回true,则MyLayout的onTouch都不会执行。
onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,
返回false代表不对事件进行拦截,默认返回false。
 
 
总结几个方法:
1、当用户触摸屏幕时,activity会接受这个事件,然后分发给下一级(view容器),view容器再分发给自己的onInterceptTouchEvent,如果返回结果是false(也就是不拦截),事件就会再传递给下一级(view控件),当view控件发现下面没得传了,也就是已经是最底层的view,此时该view就会消化该事件,即将事件传递给自己的onTouchEvent处理,若这时onTouchEvent处理不了该事件(返回false),这时事件传递到上一级的onTouchEvent,若还处理不了,继续向上传递;
 
2、当事件被onInterceptTouchEvent拦截时,事件就不会往下传递了,先把事件传递给自己的onTouchEvent消化,若消化不了,还是同样的规则往上传;
 
3、dispatchTouchEvent()方法中有记忆的功能,事件往下传递的时候,他会记录传递是否成功,这是根据当事件往上传时,就说明下面的view传递不成功或处理不成功;当第二次事件向下传递到处理不了的View,该View的dispatchTouchEvent()方法会判断,就不会往下传递了,直接把事件交给自己的onTouchEvent()方法来处理;如果上次的事件由下面的view成功处理了,那么这次事件继续往下传递; 
 
值得注意的是:dispatchTouchEvent的记忆功能是一系列的事件完成的时候才有效,过了就无效了,例如,触碰屏幕ACTION_DOWN , 然后滑动ACTION_MOVE , 最后抬起手ACTION_UP,这三个动作为一系列的事件,当你ACTION_DOWN时,dispatchTouchEvent就会记录你传递事件的“痕迹”,接着按着这个记忆来传递你的ACTION_MOVE 和 ACTION_UP,注意要一系列完成的动作才构成这一记忆事件,事件过后,dispatchTouchEvent自动消除记忆。
 
 
posted @ 2015-02-14 00:32  咖啡馆的水果拼盘  阅读(386)  评论(0编辑  收藏  举报