Android 事件传递机制进阶

1.描述

Android中的事件在表现形式上有很多。如onTouch(触摸)、onClick(点击)和onLongClick(长按)等等。在具体微观上的表现形势有action_down、action_move和action_up等等。

 

 
无论哪种事件表现类型,首先都是基于事件的传递模型。其实Android中的事件传递有点类似于JS中事件传递模型。都是基于先捕获然后冒泡的形式。

 

在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程。

 

 
在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。

 



主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。

 


Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件分发和事件拦截(因为它没有自己的子View)


 


所有的UI控件:Button,TextView等等都是继承View


所有的
布局控件:RelativeLayout,LinearLayout等等继承ViewGroup


所有
容器控件:ListView,ScrollView等等继承于ViewGroup

 

 

 

 

所以,我们的事件操作主要就是发生在View和ViewGroup之间,那么View和ViewGroup中主要有哪些方法来对这些事件进行响应呢。

 

 

记住如下3个方法,我们通过查看View和ViewGroup的源码可以看到:



View

public boolean
dispatchTouchEvent(MotionEvent event)

public boolean
onTouchEvent(MotionEvent event) 

 


ViewGroup

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event) 

public boolean onInterceptTouchEvent(MotionEvent event)

 

 


三个方法说明:

public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event

public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event

public boolean onTouchEvent(MotionEvent ev); //用来处理event

 

也就是说在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中还有一个onInterceptTouchEvent方法,这些方法的返回值全部都是boolean型,为什么是boolean型呢,看看本文的标题,“事件传递”,传递的过程就是一个接一个,那到了某一个点后是否要继续往下传递呢?你发现了吗,“是否”二字就决定了这些方法应该用boolean来作为返回值。没错,这些方法都返回true或者是false。在Android中,所有的事件都是从开始经过传递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。

 

 

 

2.代码说明

 

2.1.View级别

此demo描述自定义了一个button 重写了dispatchTouchEvent和onTouchEvent方法 在activity页面中触摸,点击此button 并且activity也重写了dispatchTouchEvent和onTouchEvent方法

 

 

自定义button

public class MyButtons extends android.support.v7.widget.AppCompatButton {



    public MyButtons(Context context) {

        super(context);

    }


    public MyButtons(Context context, AttributeSet attrs) {

        super(context, attrs);

    }


    public MyButtons(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        int action=event.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Button----<dispatchTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Button----<dispatchTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Button----<dispatchTouchEvent>----抬起!!!");

                break;

        }

        return super.dispatchTouchEvent(event);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action=event.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Button----<onTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Button----<onTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Button----<onTouchEvent>----抬起!!!");

                break;

        }

        return super.onTouchEvent(event);

    }


}

 

Activity类

public class EventsActivity extends AppCompatActivity {


    private MyButtons button;


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

        button=(MyButtons) findViewById(R.id.mybuttons);



        //button触摸事件
        button.setOnTouchListener(new View.OnTouchListener() {


            @Override
            public boolean onTouch(View arg0, MotionEvent arg1) {

                int action=arg1.getAction();

                switch(action){

                    case MotionEvent.ACTION_DOWN://按下

                        Log.d("TAG", "Button----<onTouch>----按下!!!");

                        break;

                    case MotionEvent.ACTION_MOVE://移动

                        Log.d("TAG", "Button----<onTouch>----移动!!!");

                        break;

                    case MotionEvent.ACTION_UP://抬起

                        Log.d("TAG", "Button----<onTouch>----抬起!!!");

                        break;

                }

                return false;

            }

        });


        //button点击事件
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {

                Log.d("TAG", "Button----<onClick>----点击!!!");

            }

        });

    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        int action=ev.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Activity----<dispatchTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Activity----<dispatchTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Activity----<dispatchTouchEvent>----抬起!!!");

                break;

        }

        return super.dispatchTouchEvent(ev);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action=event.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Activity----<onTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Activity----<onTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Activity----<onTouchEvent>----抬起!!!");

                break;

        }

        return super.onTouchEvent(event);

    }


}

 

*****************************************************

效果

触摸button时结果(为了避免太多输出 没有移动) 

 

由此可见(无论“按下” “移动” “抬起”)执行顺序为

Activity dispatchTouchEvent方法

——>Button dispatchTouchEvent方法

——>Button OnTouch方法

——>Button onTouchEvent方法

——>Button OnClick方法。

即顺序从Activity——>View Acctivity的onTouchEvent方法并未执行。

 

 

*****************************************************

其他不变将Button的dispatchTouchEvent方法返回true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

 

 

 

即此时从Activity传到Button的dispatchTouchEvent方法,由于此方法返回true,Button的其他方法不再执行。即不在往下传递。

 

 

 

*****************************************************

其他不变将Button的onTouch方法返回true

 

Button的onTouch方法返回true后,Button的onTouchEvent方法和OnClick方法不执行。

 

 

 

*****************************************************

其他不变将Button的onTouchEvent方法返回true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

 

Button的onTouchEvent方法返回true时OnClick方法不执行。

 

 

*****************************************************

此时其他不变将Activity的dispatchTouchEvent的返回值变成true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

 

 

即此时如论Button的其他方法返回值如何,只要设置了Activity的dispatchTouchEvent的返回值变成true,Button的所有方法都不会执行。

 

 

*****************************************************

 

此时其他不变将Activity的onTouchEvent的返回值变成true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

和返回super.onTouchEvent(event)时没有区别。

 

源码

 

Activity.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {

            onUserInteraction();

        }

        if (getWindow().superDispatchTouchEvent(ev)) {

            return true;

        }

        return onTouchEvent(ev);

}

 

 

从源码中可以看到,dispatchTouchEvent方法只处理了ACTION_DOWN事件,前面提到过,所有的事件都是以按下为起点的,所以,Android认为当ACTION_DOWN事件没有执行时,后面的事件都是没有意义的,所以这里首先判断ACTION_DOWN事件。如果事件成立,则调用了onUserInteraction方法,该方法可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响,接着会判断getWindow().superDispatchTouchEvent(ev)的执行结果,看看它的源码:

 

 

 

/**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */



public abstract boolean superDispatchTouchEvent(MotionEvent event);

 

 

 

通过源码注释我们可以了解到这是个抽象方法,用于自定义的Window,例如自定义Dialog传递触屏事件,并且提到开发者不需要去实现或调用该方法,系统会完成,如果我们在MainActivity中将dispatchTouchEvent方法的返回值设为true,那么这里的执行结果就为true,从而不会返回执行onTouchEvent(ev),如果这里返回false,那么最终会返回执行onTouchEvent方法,由此可知,接下来要调用的就是onTouchEvent方法了。

 

通过日志输出信息可以看到,ACTION_DOWN事件从Activity被分发到了MyButton,接着执行了onTouch和onTouchEvent方法,为什么先执行onTouch方法呢?

原因:Android 事件传递机制初涉

 

 

当我们的手从屏幕抬起时,会发生ACTION_UP事件。从之前输出的日志信心中可以看到,ACTION_UP事件同样从Activity开始到myButton进行分发和处理,最后,由于我们注册了onClick事件,当onTouchEvent执行完毕后,就调用了onClick事件,那么onClick是在哪里被调用的呢?

Android 事件传递机制初涉

 

我们可以知道在Android中,事件是通过层级传递的,一次事件传递对应一个完整的层级关系,例如上面分析的ACTION_DOWN事件从Activity传递到MyButton,ACTION_UP事件也同样。结合源码分析各个事件处理的方法,也可以明确看到事件的处理流程。


 

到目前为止,Android中的事件拦截机制就分析完了。但这里我们只讨论了单布局结构下单控件的情况,如果是嵌套布局,那情况又是怎样的呢?接下来我们就在嵌套布局的情况下对Android的事件传递机制进行进一步的探究和分析。

 

 

 

 

2.2.ViewGroup级别

新建一个类myLayout继承于LinearLayout,同样重写dispatchTouchEvent和onTouchEvent方法,另外,还需要重写onInterceptTouchEvent方法,在文章开头介绍过,这个方法只有在ViewGroup中才存在,作用是控制是否需要拦截事件。这里不要和dispatchTouchEvent弄混淆了,后者是控制对事件的分发,并且后者要先执行。那么,事件是先传递到View呢,还是先传递到ViewGroup的?通过下面的分析我们可以得出结论。

 

自定义Linearlayout

public class MyLinearlayout extends LinearLayout {




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




    public MyLinearlayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }




    public MyLinearlayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    @Override

    public boolean dispatchTouchEvent(MotionEvent event) {

        int action=event.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----抬起!!!");

                break;

        }

        return super.dispatchTouchEvent(event);

    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int action=ev.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----抬起!!!");

                break;

        }

        return super.onInterceptTouchEvent(ev);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action=event.getAction();

        switch(action){

            case MotionEvent.ACTION_DOWN://按下

                Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----按下!!!");

                break;

            case MotionEvent.ACTION_MOVE://移动

                Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----移动!!!");

                break;

            case MotionEvent.ACTION_UP://抬起

                Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----抬起!!!");

                break;

        }

        return super.onTouchEvent(event);

    }


}

 

 

 

java代码

layout=(MyLinearlayout) findViewById(R.id.mylinearlayout);

//layout触摸事件

 layout.setOnTouchListener(new View.OnTouchListener() {

            

@Override

            

public boolean onTouch(View arg0, MotionEvent arg1) {

         int action=arg1.getAction();

          switch(action){

              case MotionEvent.ACTION_DOWN://按下

                  Log.d("TAG", "layout----<onTouch>----按下!!!");

                  break;

               case MotionEvent.ACTION_MOVE://移动

                  Log.d("TAG", "layout----<onTouch>----移动!!!");

                  break;

              case MotionEvent.ACTION_UP://抬起

                  Log.d("TAG", "layout----<onTouch>----抬起!!!");

                  break;

           }

   return false;

 }

});



//layout点击事件
layout.setOnClickListener(new View.OnClickListener() {


    @Override
     public void onClick(View arg0) {

         Log.d("TAG", "layout----<onClick>----点击!!!");

       }
 });

由于上面讲述了Activity到View的事件传递 此处之操作ViewGroup到View的传递

 

 

*****************************************************

效果

触摸button时结果(为了避免太多输出 没有移动) 

由此可见(无论“按下” “移动” “抬起”)执行顺序为

Activity dispatchTouchEvent方法

——>LinearLayout dispatchTouchEvent方法

——>LinearLayout  onInterceptTouchEvent方法

——>Button dispatchTouchEvent方法

——>Button OnTouch方法

——>Button onTouchEvent方法

——>Button OnClick方法。

LinearLayout的点击事件并未执行。ViewGroup的onTouchEvent也没执行。

 

 

 

 

 

*****************************************************

其他不变将LinearLayout的dispatchTouchEvent方法返回true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

LinearLayout的dispatchTouchEvent方法返回true,事件不往下传递。

 

 

 

 

*****************************************************

其他不变将LinearLayout的onInterceptTouchEvent方法返回true

效果

触摸button时结果(为了避免太多输出 没有移动) 

LinearLayout的onInterceptTouchEvent方法返回true,即ViewGroup将事件拦截下来不传给自己的子View,而是自己消费。ViewGroup的onTouchEvent也没执行。

 

如果ViewGroup的onInterceptTouchEvent方法返回true,即将事件传递给自己的子View,如第一次。

 

 

在紧挨着上面的基础上将LinearLayout的OnTouch返回true

 

效果

触摸button时结果(为了避免太多输出 没有移动) 

 

和View一样OnTouch返回true,ViewGroup的点击事件不执行。

 

 

以上我们对Android事件传递机制进行了分析,期间结合系统源码对事件传递过程中的处理情况进行了探究。通过单布局情况和嵌套布局情况下的事件传递和处理进行了分析,现总结如下:


Android中事件传递按照从上到下进行层级传递,事件处理从Activity开始到ViewGroup再到View。
事件传递方法包括dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent,其中前两个是View和ViewGroup都有的,最后一个是只有ViewGroup才有的方法。这三个方法的作用分别是负责事件分发、事件处理、事件拦截。
onTouch事件要先于onClick事件执行,onTouch在事件分发方法dispatchTouchEvent中调用,而onClick在事件处理方法onTouchEvent中被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值