Android-点击事件分发机制验证

简介

点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生之后,系统需要这个事件传递给一个具体的View,而这个传递过程就是分发过程。

点击事件的分发过程由三个重要方法共同完成:
  • dispatchTouchEvent 事件分发
  • onInterceptTouchEvent 事件拦截
  • onTouchEvent 事件响

方法介绍

public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受到当前View的onTouchEvent和下级View的方法的影响。
  • 如果return true,表示点击事件被消耗掉。
  • 如果return false,表示点击事件没有被消耗

public boolean onInterceptTouchEvent(MotionEvent ev) 
用来判断是否拦截某个事件。
  • 如果return true,表示点击事件被拦截并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理
  • 如果return false,表示点击事件未被拦截,View上的事件会被传递到子View上,由子View的dispatchTouchEvent来处理。

public boolean onTouchEvent(MotionEvent ev)
用来处理点击事件,返回结果表示是否消耗当前事件。
  • 如果return true,表示点击事件被接收并消费。
  • 如果return false,表示点击事件未被消费。

三个方法之间的关系

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

        return consume;
    }

代码实践

界面UI布局

Android-点击事件分发机制验证_第1张图片

ParentLayout

后面例子,都是在此代码的基础上修改的
public  class  ParentLayout  extends  LinearLayout {

    public  ParentLayout( Context  context) { super(context);}

    public  ParentLayout( Context  contextAttributeSet  attrs) { super(context, attrs);}

    public  ParentLayout( Context  contextAttributeSet  attrsint  defStyle) { super(context, attrs, defStyle);}

    @Override
    public  boolean  dispatchTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .dispatchTouchEvent(ev);
    }

    @Override
    public  boolean  onInterceptTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .onInterceptTouchEvent(ev);
    }

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .onTouchEvent(ev);
    }
}

ChildView

后面例子,都是在此代码的基础上修改的
public  class  ChildView  extends  TextView {

    public  ChildView( Context  context) { super(context);}

    public  ChildView( Context  contextAttributeSet  attrs) { super(context, attrs);}

    public  ChildView( Context  contextAttributeSet  attrsint  defStyle) { super(context, attrs, defStyle);}

    @Override
    public  boolean  dispatchTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .dispatchTouchEvent(ev);
    }

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .onTouchEvent(ev);
    }
}

打印结果

点击ChildView(1处),打印Log 

点击ParentView(2处),打印Log

处理过程解释

代码实践这种情况下,这三个方法,默认都发送false。对于三个方法return的介绍参考前面方法介绍。这里,我们以点击ChildView为例子,再次强调点击实践的分发流程: 

Android-点击事件分发机制验证_第2张图片dispatchTouchEvent方法再探

事件分发-false

在ChildView代码基础上,将dispatchTouchEvent方法内容改为如下形式
public  class  ChildView  extends  TextView {
    . ..
    @Override
    public  boolean  dispatchTouchEvent( MotionEvent  ev) {
        ABLog .e( "Default Return "  +  String .valueOf( "false"));
        return false;
    }
点击ChildView(1处),打印Log 

事件分发-true

在ChildView代码基础上,将dispatchTouchEvent方法内容改为如下形式
public  class  ChildView  extends  TextView {

    @Override
    public  boolean  dispatchTouchEvent( MotionEvent  ev) {
        ABLog .e( "Default Return "  +  String .valueOf( "true"));
        return true;
    }
点击ChildView(1处),打印Log
触发两次Log打印?因为当ChildView的dispatchTouchEvent返回true时,这就表示一系列点击事件都由ChildView处理,所以,两次Log分别为手势的Down和Up事件。而如果返回false时,这就表示一系列点击事件不由ChildView处理,所以,只打印一次。
 

onTouchEvent方法再探

事件响应-false

在ChildView代码基础上,将onTouchEvent方法内容改为如下形式
public  class  ChildView  extends  TextView {

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev) {
        ABLog .e( "Default Return "  +  String .valueOf( "false"));
        return false;
    }
点击ChildView(1处),打印Log
和默认效果一样,因为默认也是返回false。 



事件响应-true

在ChildView代码基础上,将onTouchEvent方法内容改为如下形式
public  class  ChildView  extends  TextView {

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev) {
        ABLog .e( "Default Return "  +  String .valueOf( "true"));
        return true;
    }
点击ChildView(1处),打印Log
触发两次Log打印?原因同上,这一系列事件都有ChildView出发。 
Android-点击事件分发机制验证_第3张图片

onInterceptTouchEvent方法再探

事件拦截-false

在ParentLayout代码基础上,将onInterceptTouchEvent方法内容改为如下形式
public  class  ParentLayout  extends  LinearLayout {

    @Override
    public  boolean  onInterceptTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "false"));
        return  false;
    }
点击ChildView(1处),打印Log 


点击ParentView(2处),打印Log

总结一下上面Log情况?Log结果都和一开始全部默认情况相同。onInterceptTouchEvent为false的情况,就是调用super.onInterceptTouchEvent的情况。onInterceptTouchEvent返回false,表示点击事件不拦截,向子View传递。

事件拦截-true 事件响应-false

在ParentLayout代码基础上,将ParentLayout改为如下形式
public class ParentLayout extends LinearLayout {

   
@Override
   
public boolean dispatchTouchEvent(MotionEvent ev){
       
ABLog.e("Default Return " + String.valueOf("default: false"));

       
return super.dispatchTouchEvent(ev);
    }

   
@Override
   
public boolean onInterceptTouchEvent(MotionEvent ev){
       
ABLog.e("Default Return " + String.valueOf("true"));
       
return true;
    }

   
@Override
   
public boolean onTouchEvent(MotionEvent ev){
       ABLog.e("Default Return " + String.valueOf("false"));
       return false;
    }
}
点击ChildView(1处)或者ParentLayout(2处),打印Log
为什么点击ChildView和ParentLayout显示相同Log?因为MotionEvent一系列事件,已经被onInterceptTouchEvent拦截了,所以MotionEvent一系列事件将不会被传递到子View(ChildView)中。
为什么拦截了点击事件,但是Log只出现了一次?因为使用onInterceptTouchEvent拦截了点击事件,只是拦截了点击事件的传递,并没有处理MotionEvent的一系列事件。更进一步说,只有dispatchTouchEvent方法返回true,才表示MotionEvent的一系列事件被处理。onTouchEvent方法返回true,只是间接的对dispatchTouchEvent产生了影响。 

事件拦截-true 事件响应-true

在ParentLayout代码基础上,将ParentLayout改为如下形式
public  class  ParentLayout  extends  LinearLayout {
    @Override
    public  boolean  dispatchTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "default: false"));

        return  super .dispatchTouchEvent(ev);
    }

    @Override
    public  boolean  onInterceptTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "true"));
        return true;
    }

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev){
        ABLog .e( "Default Return "  +  String .valueOf( "true"));
        return true;
    }
}
点击ChildView(1处)或者ParentLayout(2处),打印Log
为什么Log和之前看到的不一样?对于这次的Log,其实还是包含了点击事件Down和Up。在这我们用分割线分开,第一部分,出现了dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent三个函数;第二部分,因为onInterceptTouchEvent(拦截方法),只被调用一次哦,所以只出现了dispatchTouchEvent, onTouchEvent两个函数。 

View中Listener的优先级

主要探究OnTouchListener和OnClickListener还有onTouchEvent三个方法之间的关系: 
Android-点击事件分发机制验证_第4张图片

界面

Android-点击事件分发机制验证_第5张图片

ChildView

public  class  ChildView  extends  TextView {

    public  ChildView( Context  context) {
        super(context);
    }

    public  ChildView( Context  contextAttributeSet  attrs) {
        super(context, attrs);
    }

    public  ChildView( Context  contextAttributeSet  attrsint  defStyle) { super(context, attrs, defStyle);}

    @Override
    public  boolean  onTouchEvent( MotionEvent  ev) {
        ABLog .e( "Default Return "  +  String .valueOf( "false"));
        return  super .onTouchEvent(ev);
    }
}

MainActivity

public   class  MainActivity  extends   AppCompatActivity  {

   
@Override
   
protected   void   onCreate ( Bundle   savedInstanceState ) {
       
super . onCreate(savedInstanceState);
        setContentView(
R . layout . activity_main);

       
ChildView  view  =  ( ChildView )findViewById( R . id . myChildView);
        view
. setOnClickListener( new   View . OnClickListener () {
           
@Override
           
public   void   onClick ( View   v ) {
               
ABLog . e( "OnClickListener" );
            }
        });

        view
. setOnTouchListener( new   View . OnTouchListener () {
           
@Override
           
public   boolean   onTouch ( View   v MotionEvent   event ) {
               
ABLog . e( "setOnTouchListener" );
               
return   false ;
            }
        });
    }
}
点击ChildView(1处),打印Log
前一部分,为Action_Down触发;后一部分,为Action_Up触发。 


onTouch方法再探

将onTouch方法返回值改为true,表示处理点击时间的一系列事件,将setOnTouchListener方法改为:
view .setOnTouchListener( new  View. OnTouchListener() {
            @Override
            public  boolean  onTouch( View  vMotionEvent  event) {
                ABLog .e( "setOnTouchListener");
                return true;
            }
        });
点击ChildView(1处),打印Log
点击时间在OnTouch方法中被处理,所以不会传到onTouchEvent方法中去了。因此更加不会传到OnClickListener监听方法中去了。
一个为Action_Down触发,一个为Action_Up触发。 




你可能感兴趣的:(android,touch,点击事件流程)