当前位置: 萬仟网 > IT编程>移动开发>Android > 理解并测试什么是Android事件分发

理解并测试什么是Android事件分发

2020年05月09日  | 萬仟网IT编程  | 我要评论

一、什么是事件分发

所谓事件分发,就是将一次完整的点击所包含的点击事件传递到某个具体的view或viewgroup,让该view或该viewgroup处理它(消费它)。分发是从上往下(父到子)依次传递的,其中可能经过的对象有最上层activity,中间层viewgroup,最下层view。

二、activity的层次结构

源码查找:
1.自己的activity的setcontentview()方法

 @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_event_distribution);
    }

2.跳转到activity.java的setcontentview()方法,可以看到,调用了getwindow()的方法

  public void setcontentview(@layoutres int layoutresid) {
        getwindow().setcontentview(layoutresid);
        initwindowdecoractionbar();
    }

3.activity.java的mwindow来自phonewindow

 mwindow = new phonewindow(this, window, activityconfigcallback);

4.phonewindow.java-->setcontentview()--> installdecor(),在phonewindow中调用了installdecor()方法

  @override
    public void setcontentview(int layoutresid) {
        // note: feature_content_transitions may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. do not check the feature
        // before this happens.
        if (mcontentparent == null) {
            installdecor(); //继续执行
        } else if (!hasfeature(feature_content_transitions)) {
            mcontentparent.removeallviews();
        }
..................

5.phonewindow.java-->setcontentview()--> installdecor()--> generatelayout(mdecor),在 installdecor()中又继续执行了generatelayout(mdecor)方法。

 mcontentparent = generatelayout(mdecor);

6.phonewindow.java-->generatelayout()

viewgroup generatelayout(decorview decor)

7.phonewindow.java-->generatelayout()--> int layoutresource,layoutresource根据不同情况,返回不同的资源文件,也就是布局文件。

  int layoutresource;

8.phonewindow.java-->generatelayout()-->r.layout.screen_title; 拿出一个常用的布局文件,screen_title.xml

 layoutresource = r.layout.screen_title;

9.screen_title.xml的代码, viewstub是用来显示actionbar的,另外两个framelayout,一个显示titleview,一个显示contentview,平时写的内容,正是contentview

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitssystemwindows="true">
    <!-- popout bar for action modes -->
    <viewstub android:id="@+id/action_mode_bar_stub"
              android:inflatedid="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionbartheme" />
    <framelayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowtitlesize"
        style="?android:attr/windowtitlebackgroundstyle">
        <textview android:id="@android:id/title" 
            style="?android:attr/windowtitlestyle"
            android:background="@null"
            android:fadingedge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </framelayout>
    <framelayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundgravity="fill_horizontal|top"
        android:foreground="?android:attr/windowcontentoverlay" />
</linearlayout>

如以下结构图:

image.png

三、事件分发涉及到的主要方法

涉及到的方法

	@override
    public boolean dispatchtouchevent(motionevent ev) {
        //分发事件
        return super.dispatchtouchevent(ev);
    }

    @override
    public boolean onintercepttouchevent(motionevent ev) {
        //拦截事件
        return super.onintercepttouchevent(ev);
    }

    @override
    public boolean ontouchevent(motionevent event) {
        //消费事件
        return super.ontouchevent(event);
    }

activity涉及到的方法:dispatchtouchevent()、ontouchevent()

viewgroup涉及到的方法:dispatchtouchevent()、onintercepttouchevent()

view涉及到的方法:dispatchtouchevent()、ontouchevent()

四、事件分发流程

1.activity把事件分发到viewgroup

(1)事件传递

每一次事件分发,都是从dispatchtouchevent()开始的。

1)查看activity的源码,调用了getwindow().superdispatchtouchevent(ev)

public boolean dispatchtouchevent(motionevent ev) {
        if (ev.getaction() == motionevent.action_down) {
            onuserinteraction();
        }
        if (getwindow().superdispatchtouchevent(ev)) {
            return true;
        }
        return ontouchevent(ev);
    }

2)在activity.java中可以看到,所以getwindow().superdispatchtouchevent(ev)实际上是调用了phonewindow.java中的superdispatchtouchevent(ev)方法。

 public window getwindow() {
        return mwindow;
    }

 mwindow = new phonewindow(this, window, activityconfigcallback); //mwindow的定义

3)然后再看phonewindow.java中的superdispatchtouchevent(ev)方法,是调用decorview.java的mdecor.superdispatchtouchevent(event)

  @override
    public boolean superdispatchtouchevent(motionevent event) {
        return mdecor.superdispatchtouchevent(event);
    }

4)而decorview是继承framelayout,再继承viewgroup的

private decorview mdecor; //实例对象
class decorview extends framelayout; //继承framelayout
 framelayout extends viewgroup; //继承viewgroup

5)从上面四步来分析,avtivity的getwindow().superdispatchtouchevent()方法最后调用的是viewgroup的dispatchtouchevent()方法,从而实现了事件从activity的dispatchtouchevent()向下传递到viewgroup的dispatchtouchevent()方法。

(2)总结

6)返回值分析。

  • 如果avtivity的getwindow().superdispatchtouchevent()返回true,则avtivity的dispatchtouchevent(),也会返回true,表示点击事件顺利分发给viewgroup,由viewgroup继续进行下一层的分发,avtivity的分发任务结束。
  • 如果返回false,表示此次点击事件由avtivity层消费,会执行avtivity的ontouchevent(),无论ontouchevent()这个方法返回的是true或者false,本次的事件分发都结束了。

(3)流程图

事件分发.png

2.viewgroup把事件分发到viewgroup或view

(1)事件拦截

viewgroup.java中的部分代码
viewgroup-->dispatchtouchevent()

public boolean dispatchtouchevent(motionevent ev) {  			
				if (!disallowintercept) {
                    intercepted = onintercepttouchevent(ev);
                    ev.setaction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
 }

方法中使用了onintercepttouchevent(ev)方法

  • 如果返回true,则表示viewgroup拦截此次事件。
  • 如果返回false,则表示viewgroup不拦截,事件继续往下分发。
  • onintercepttouchevent(ev)默认返回不拦截,可以在viewgroup中重写改方法来拦截事件。
  • 不拦截事件,则会调用viewgroup的ontouchevent()来处理点击事件,把事件消费掉。

(2)分发

这个源码中,使用到了intercepted这个变量,主要作用是来遍历子viewgroup和view,

  • 当intercepted为false的时候,遍历子viewgroup和子view,因为这个事件没有被消费掉,继续分发到子viewgroup和子view。
  • 当intercepted为true的时候,该事件已经被消费,不会继续往下分发,也不会遍历子viewgroup和子view,也不会执行if语句里面的方法。
  • 进入if语句中判断点击事件的触摸范围(焦点)是否属于某个子viewgroup或者子view。
  • 如果触摸范围属于子view,则调用子view的dispatchtouchevent()方法。
  • 如果触摸范围属于子viewgroup,则继续遍历下一层的viewgroup或者view。
  • 遍历到最下层的view,还是找不到消费此处事件的view,则依次回调上一层的viewgroup的ontouchevent()方法,直到回调到activity的ontouchevent()方法。
 			// check for interception.
            final boolean intercepted;

			if (!canceled && !intercepted) {

                // if the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // we are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                view childwithaccessibilityfocus = ev.istargetaccessibilityfocus()
                        ? findchildwithaccessibilityfocus() : null;

(3)流程图

viewgroup事件分发.png

3.view的事件分发

(1)分析

view的dispatchtouchevent()的源码

 public boolean dispatchtouchevent(motionevent event) {
        // if the event should be handled by accessibility focus first.
        if (event.istargetaccessibilityfocus()) {
            // we don't have focus or no virtual descendant has it, do not handle the event.
            if (!isaccessibilityfocusedvieworhost()) {
                return false;
            }
            // we have focus and got the event, then use normal event dispatch.
            event.settargetaccessibilityfocus(false);
        }

        boolean result = false;

        if (minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.ontouchevent(event, 0);
        }

        final int actionmasked = event.getactionmasked();
        if (actionmasked == motionevent.action_down) {
            // defensive cleanup for new gesture
            stopnestedscroll();
        }

        if (onfiltertoucheventforsecurity(event)) {
            if ((mviewflags & enabled_mask) == enabled && handlescrollbardragging(event)) {
                result = true;
            }
            //noinspection simplifiableifstatement
            listenerinfo li = mlistenerinfo;
            if (li != null && li.montouchlistener != null
                    && (mviewflags & enabled_mask) == enabled
                    && li.montouchlistener.ontouch(this, event)) {
                result = true;
            }

            if (!result && ontouchevent(event)) {
                result = true;
            }
        }

        if (!result && minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.onunhandledevent(event, 0);
        }

        // clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an action_down but we didn't want the rest
        // of the gesture.
        if (actionmasked == motionevent.action_up ||
                actionmasked == motionevent.action_cancel ||
                (actionmasked == motionevent.action_down && !result)) {
            stopnestedscroll();
        }

        return result;
    }

  • 在view的dispatchtouchevent()方法中首先会调用ontouch()方法,如果ontouch()方法能够消费该事件,就会直接返回true,从而直接结束view的dispatchtouchevent()方法,不再执行ontouchevent()方法;
  • 如果ontouch()方法不能消费该事件,就会返回false,从而继续执行ontouchevent``()方法。
  • 如果ontouchevent()能够消费该事件,就会返回true从而直接结束dispatchtouchevent()方法。
  • 如果ontouchevent()方法也不能消费该事件,就会返回默认的false从而回调到上一层viewgroup的ontouchevent()方法,直到回调到activity的ontouchevent``()方法。

(2)流程图

view的事件分发.png

五、具体例子

(0)测试代码

共有三种类型和四个测试代码

activity:eventdistributionactivity

viewgroup:eventdistributionlinearlayout1、eventdistributionlinearlayout2

view:eventdistributionbutton

分别代码:
eventdistributionactivity.java

public class eventdistributionactivity extends baseactivity {
    button mbtn;

    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_event_distribution);
        mbtn = findviewbyid(r.id.btn);
        onclick();
    }

    public void onclick() {
        mbtn.setonclicklistener(new view.onclicklistener() {
            @override
            public void onclick(view v) {
                log.v("showlog", "按钮被点击!");
            }
        });

        mbtn.setontouchlistener(new view.ontouchlistener() {
            @override
            public boolean ontouch(view v, motionevent event) {
                boolean dis = false;
                log.v("showlog", "button.touch()=" + dis);
                return dis;
            }
        });
    }

    @override
    public boolean dispatchtouchevent(motionevent ev) {
        //分发事件
        boolean dis = super.dispatchtouchevent(ev);
        log.v("showlog", "activity.dispatchtouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean ontouchevent(motionevent event) {
        //处理事件
        boolean dis = super.ontouchevent(event);
        log.v("showlog", "activity.ontouchevent()=" + dis);
        return dis;
    }

}

eventdistributionlinearlayout1.java

public class eventdistributionlinearlayout1 extends linearlayout {
    public eventdistributionlinearlayout1(context context, attributeset attrs) {
        super(context, attrs);
    }

    @override
    public boolean dispatchtouchevent(motionevent ev) {
        //分发事件
        boolean dis = super.dispatchtouchevent(ev);
        log.v("showlog", "linearlayout1.dispatchtouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean onintercepttouchevent(motionevent ev) {
        //拦截事件
        boolean dis = super.onintercepttouchevent(ev);
        log.v("showlog", "linearlayout1.onintercepttouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean ontouchevent(motionevent event) {
        //消费事件
        boolean dis = super.ontouchevent(event);
        log.v("showlog", "linearlayout1.ontouchevent()=" + dis);
        return dis;
    }
}

eventdistributionlinearlayout2.java

public class eventdistributionlinearlayout2 extends linearlayout {
    public eventdistributionlinearlayout2(context context, attributeset attrs) {
        super(context, attrs);
    }

    @override
    public boolean dispatchtouchevent(motionevent ev) {
        //分发事件
        boolean dis = super.dispatchtouchevent(ev);
        log.v("showlog", "linearlayout2.dispatchtouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean onintercepttouchevent(motionevent ev) {
        //拦截事件
        boolean dis = super.onintercepttouchevent(ev);
        dis = true;
        log.v("showlog", "linearlayout2.onintercepttouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean ontouchevent(motionevent event) {
        //消费事件
        boolean dis = super.ontouchevent(event);
        log.v("showlog", "linearlayout2.ontouchevent()=" + dis);
        return dis;
    }
}

eventdistributionbutton.java

public class eventdistributionbutton extends button {
    public eventdistributionbutton(context context, attributeset attrs) {
        super(context, attrs);
    }
    @override
    public boolean dispatchtouchevent(motionevent event) {
        //分发事件
        boolean dis = super.dispatchtouchevent(event);
        log.v("showlog", "button.dispatchtouchevent()=" + dis);
        return dis;
    }
    
    @override
    public boolean ontouchevent(motionevent event) {
        //消费事件
        boolean dis = super.ontouchevent(event);
        log.v("showlog", "button.ontouchevent()=" + dis);
        return dis;
    }

    @override
    public boolean performclick() {
        boolean dis = super.performclick();
        log.v("showlog", "button.performclick()="+dis);
        return dis;
    }

}

activity_event_distribution.xml

<?xml version="1.0" encoding="utf-8"?>
<com.lanjiabin.systemtest.event.eventdistributionlinearlayout1 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".event.eventdistributionactivity">

    <com.lanjiabin.systemtest.event.eventdistributionlinearlayout2
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.lanjiabin.systemtest.event.eventdistributionbutton
            android:background="@drawable/button_color_circle_shape1"
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margintop="300dp"
            android:text="点击" />

    </com.lanjiabin.systemtest.event.eventdistributionlinearlayout2>

</com.lanjiabin.systemtest.event.eventdistributionlinearlayout1>

效果图:一个linearlayout1包含linearlayout2再包含一个button

界面只有一个按钮

image.png

(1)测试1

测试用例:按钮消费事件,和空白处不消费事件

按住按钮不松开,事件被button的ontouchevent()消费

linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=false
button.touch()=false
button.ontouchevent()=true
button.dispatchtouchevent()=true
linearlayout2.dispatchtouchevent()=true
linearlayout1.dispatchtouchevent()=true
activity.dispatchtouchevent()=true

按住空白处不松开,没有事件被消费

linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=false
linearlayout2.ontouchevent()=false
linearlayout2.dispatchtouchevent()=false
linearlayout1.ontouchevent()=false
linearlayout1.dispatchtouchevent()=false
activity.ontouchevent()=false
activity.dispatchtouchevent()=false

(2)测试2

测试用例:在linearlayout2处截断

修改代码:eventdistributionlinearlayout2.java

 @override
    public boolean onintercepttouchevent(motionevent ev) {
        //拦截事件
        boolean dis = super.onintercepttouchevent(ev);
        dis = true;
        log.v("showlog", "linearlayout2.onintercepttouchevent()=" + dis);
        return dis;
    }

按住按钮不松开:事件截断生效,将不会继续遍历下层的viewgroup或者view,所以日志中看不到button的日志打印。

linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=true  //截断生效
linearlayout2.ontouchevent()=false
linearlayout2.dispatchtouchevent()=false
linearlayout1.ontouchevent()=false
linearlayout1.dispatchtouchevent()=false
activity.ontouchevent()=false
activity.dispatchtouchevent()=false

(3)测试3

测试用例:在view中ontouch()中返回true

也就是在button中设置ontouch()返回true,则不会产生点击事件,完整的点击事件是被按下和松开的,所以上面没有点击按钮的监听事件的打印日志。

首先,看看完整的点击事件日志,去掉先前测试的改变的代码。

linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=false
button.touch()=false
button.ontouchevent()=true  //触摸按下事件被消费
button.dispatchtouchevent()=true
linearlayout2.dispatchtouchevent()=true
linearlayout1.dispatchtouchevent()=true
activity.dispatchtouchevent()=true  //触摸按下的事件处理结束
linearlayout1.onintercepttouchevent()=false  //开始触摸i抬起的事件
linearlayout2.onintercepttouchevent()=false
button.touch()=false
button.ontouchevent()=true //触摸抬起的事件被消费
button.dispatchtouchevent()=true
linearlayout2.dispatchtouchevent()=true
linearlayout1.dispatchtouchevent()=true
activity.dispatchtouchevent()=true
按钮被点击!  //onclick
button.performclick()=true

开始测试用例:

修改代码:
eventdistributionactivity.java,将boolean dis = false;修改为boolean dis = true;

  mbtn.setontouchlistener(new view.ontouchlistener() {
            @override
            public boolean ontouch(view v, motionevent event) {
                boolean dis = true;
                log.v("showlog", "button.touch()=" + dis);
                return dis;
            }
        });

按下和松开按钮:可以看到,事件被button.touch()消费了,因为在touch()返回了true,事件没有继续传递下去,所以onclick事件没有被触发,没有生效。

linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=false
button.touch()=true  //触摸事件被消费
button.dispatchtouchevent()=true
linearlayout2.dispatchtouchevent()=true
linearlayout1.dispatchtouchevent()=true
activity.dispatchtouchevent()=true //触摸按下事件处理完毕
linearlayout1.onintercepttouchevent()=false
linearlayout2.onintercepttouchevent()=false
button.touch()=true
button.dispatchtouchevent()=true
linearlayout2.dispatchtouchevent()=true
linearlayout1.dispatchtouchevent()=true
activity.dispatchtouchevent()=true

编程中我们会遇到多少挫折?表放弃,沙漠尽头必是绿洲。

原文:https://www.cnblogs.com/lanjiabin/p/12853407.html

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
Copyright © 2020  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com