欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-14 14:06:55
一、什么是事件分发 所谓事件分发,就是将一次完整的点击所包含的点击事件传递到某个具体的View或ViewGroup,让该View或该ViewGroup处理它(消费它)。分发是从上往下(父到子)依次传递的,其中可能经过的对象有最上层Activity,中间层ViewGroup,最下层View。 二、Ac ......

一、什么是事件分发

所谓事件分发,就是将一次完整的点击所包含的点击事件传递到某个具体的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>

如以下结构图:

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

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

涉及到的方法

	@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)流程图

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

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)流程图

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

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)流程图

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

五、具体例子

(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

界面只有一个按钮

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

(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