当前位置: 首页>移动开发>正文

Android key事件传输流程 android 事件传递

今天抽空去研究了一下Android的事件传递机制,Android的事件传递机制分为按键事件和触摸事件,关于按键事件比较简单,一个链接写的不错,看完就能明白(http://orgcent.com/android-key-event-mechanism/),而这里的事件指的是touchevent,即触摸事件。不得不说真心麻烦+恶心,而且关键还让我遇到了listview这控件

一个touchevent一般是由多个motionevent(有DOWN,UP,MOVE,CANCEL四种)构成,合理的分配这些motionevent到达指定的控件,这些控件才能够接收到相应的touchevent,然后做出处理。关于motionevent请参考我转的另一篇博文。

一.相关类和方法

1.与触摸事件有关系的类是view,viewgroup,activity。

1)这里view我们表示的是那些继承自view不能再容纳其他控件的类,比如textview,imageview。其中下面两个方法是三者都有的,且与touchevent相关的。

2)这里的viewgroup表示的是那些继承自viewgroup的类,它们的共同点是可以继续包含view。比如各种layout以及上面说到的恶心的listview。

3)这里的activity表示的就是那些继承自activity的类。

所以下面没有特殊描述,均用一个类代表它们整个群体。

2.与触摸事件有关系的方法是dispatchTouchEvent,onTouchEvent以及onInterceptTouchEvent,

public boolean dispatchTouchEvent(MotionEvent event) - 用于事件分发,三个类都有该方法

public boolean onTouchEvent(MotionEvent event) - 用于事件消费,三个类都有该方法

public boolean onInterceptTouchEvent(MotionEvent ev) - 用于拦截事件,只有viewgroup有该方法

这三个方法在三个类中的用途是一样的,但是详细的处理过程却不同。这我们将在下一部分去说明。

二.motionevent的dispatchTouchEvent流程

1.Activity部分

对于正常的理解来说,应该是activity拿到某一个motionevent,然后开始事件分发,所以我们来看看activity的dispatchTouchEvent源码

public boolean dispatchTouchEvent(MotionEvent ev) {
 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
 onUserInteraction();
 }
 if (getWindow().superDispatchTouchEvent(ev)) {
 return true;
 }
 return onTouchEvent(ev);
 }

1)Activity处理事件

对于一个手机程序来说,最先拿到手势的我觉得应该就是window了,所以首先用superDispatchTouchEvent分配motionevent到activity的内部控件,superDispatchTouchEvent实际做的事情就是FrameLayout.dispatchTouchEvent(处理流程如同下文的ViewGroup.dispatchTouchEvent),它将会去查找有没有view可以处理该事件。如果activity没有内部控件或者内部控件无法处理该motionevent时,superDispatchTouchEvent返回false,然后接收该motionevent的就是activity本身了,我们可以在Activity的onTouchEvent方法里做详细的处理。

2)Activity不处理事件

刚才1)中说到,Activity没有内部控件或者内部控件无法处理该motionevent时superDispatchTouchEvent会返回false,但是如果有内部控件切可以处理该motionevent时,将返回true,这时Activity的dispatchTouchEvent也会返回true告知系统我有一个控件接收了motionevent。

2.ViewGroup部分

superDispatchTouchEvent将事件进行分发,首先接到的当然是该Activity的Layout控件,它继承自ViewGroup。当它接收到了之后显然也要先考虑事件的分发。我们来看看ViewGroup的dispatchTouchEvent代码

public boolean dispatchTouchEvent(MotionEvent ev) {
 final int action = ev.getAction();
 final float xf = ev.getX();
 final float yf = ev.getY();
 final float scrolledXFloat = xf + mScrollX;
 final float scrolledYFloat = yf + mScrollY;
 final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 if (action == MotionEvent.ACTION_DOWN) {
 if (mMotionTarget != null) {
 // this is weird, we got a pen down, but we thought it was
 // already down!
 // XXX: We should probably send an ACTION_UP to the current
 // target.
 mMotionTarget = null;
 }
 // If we're disallowing intercept or if we're allowing and we didn't
 // intercept
 if (disallowIntercept || !onInterceptTouchEvent(ev)) {
 // reset this event's action (just to protect ourselves)
 ev.setAction(MotionEvent.ACTION_DOWN);
 // We know we want to dispatch the event down, find a child
 // who can handle it, start with the front-most child.
 final int scrolledXInt = (int) scrolledXFloat;
 final int scrolledYInt = (int) scrolledYFloat;
 final View[] children = mChildren;
 final int count = mChildrenCount;
 for (int i = count - 1; i >= 0; i--) {
 final View child = children[i];
 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
 || child.getAnimation() != null) {
 child.getHitRect(frame);
 if (frame.contains(scrolledXInt, scrolledYInt)) {
 // offset the event to the view's coordinate system
 final float xc = scrolledXFloat - child.mLeft;
 final float yc = scrolledYFloat - child.mTop;
 ev.setLocation(xc, yc);
 child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 if (child.dispatchTouchEvent(ev)) {
 // Event handled, we have a target now.
 mMotionTarget = child;
 return true;
 }
 // The event didn't get handled, try the next view.
 // Don't reset the event's location, it's not
 // necessary here.
 }
 }
 }
 }
 } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
 (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) {
 // Note, we've already copied the previous state to our local
 // variable, so this takes effect on the next event
 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
 } // The event wasn't an ACTION_DOWN, dispatch it to our target if
 // we have one.
 final View target = mMotionTarget;
 if (target == null) {
 // We don't have a target, this means we're handling the
 // event as a regular view.
 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);
 } // if have a target, see if we're allowed to and want to intercept its
 // events
 if (!disallowIntercept && onInterceptTouchEvent(ev)) {
 final float xc = scrolledXFloat - (float) target.mLeft;
 final float yc = scrolledYFloat - (float) target.mTop;
 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 ev.setAction(MotionEvent.ACTION_CANCEL);
 ev.setLocation(xc, yc);
 if (!target.dispatchTouchEvent(ev)) {
 // target didn't handle ACTION_CANCEL. not much we can do
 // but they should have.
 }
 // clear the target
 mMotionTarget = null;
 // Don't dispatch this event to our own view, because we already
 // saw it when intercepting; we just want to give the following
 // event to the normal onTouchEvent().
 return true;
 } if (isUpOrCancel) {
 mMotionTarget = null;
 } // finally offset the event to the target's coordinate system and
 // dispatch the event.
 final float xc = scrolledXFloat - (float) target.mLeft;
 final float yc = scrolledYFloat - (float) target.mTop;
 ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
 ev.setAction(MotionEvent.ACTION_CANCEL);
 target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 mMotionTarget = null;
 } return target.dispatchTouchEvent(ev);
 }

1)ViewGroup被当成普通的View

整体代码的意思是说有一个motionevent传递过来了,ViewGroup首先看自己的内部View能不能处理或者说哪个View能够处理,判断的方法是看看该motionevent是不是DOWN,如果是则看看View是否可见,如果可见再看看焦点是不是在View的内部,如果在其内部,那么我们说找到了一个View可能处理该motionevent,将其命名为target(We know we want to dispatch the event down, find a child who can handle it, start with the front-most child.),如果说没有一个View可以处理(We don't have a target, this means we're handling the event as a regular view.),此时ViewGroup将被当做普通的View来处理这个motionevent,那么将调用 super.dispatchTouchEvent(ev)方法,这里就是View的dispatchTouchEvent了。return语句返回的就是super.dispatchTouchEvent(ev)

2)ViewGroup内部有View可以处理

如果说ViewGroup内部有View可以处理,假设为target,那么将调用target.dispatchTouchEvent方法。return语句返回target.dispatchTouchEvent(ev)

3)onInterceptTouchEvent

这里有一个非常特殊的方法,就是onInterceptTouchEvent了,它可以让ViewGroup对motionevent进行拦截,意思就是我们发现某个target可以获得DOWN的焦点,但是ViewGroup不想让它内部的View处理事件,则进行拦截,此时dispatchTouchEvent返回true。

3.View部分

View部分在相对就简单一些了,在上面的target.dispatchTouchEvent之后,motionevent被传递到了View的dispatchTouchEvent中,看到这里应该也就明白了motionevent也是类似的从Activity传递到ViewGroup中的,在superDispatchTouchEvent里Framelayout.dispatchTouchEvent找到了某个View(实际是某个Layout的ViewGroup),调用了它的dispatchTouchEvent。

看View源代码中的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
 mOnTouchListener.onTouch(this, event)) {
 return true;
 }
 return onTouchEvent(event);
 }

因为View表示没有任何其他内部的控件了,所以它只有两种选择,这里首先将使用我们开发人员定义的OnTouchListener进行处理,如果可以处理,那么将返回true,如果不能处理将使用默认的onTouchEvent来处理。

三.motionevent的onTouchEvent流程

最底层的View的dispatchTouchEvent会调用onTouchListener来进行处理motionevent,或者使用onTouchEvent来处理motionevent,不论哪种都默认会返回true。所以这时ViewGroup的dispatchTouchEvent返回值为true,所以Activity的dispatchTouchEvent的返回值是true。

如果我们没定义自己的onTouchListener,并且重写了onTouchEvent,返回一个false,那么ViewGroup的dispatchTouchEvent返回为false,Activity将会调用它的onTouchEvent方法。

四.后续的motionevent

如果motionevent为DOWN的时候View没有处理,即在它的dispatchTouchEvent内返回了false,那么该View的容器ViewGroup不会再调用该View的dispatchTouchEvent了,即它将无法接收到后续的MOVE,UP。只有DOWN的时候被View处理了(在dispatchTouchEvent返回true),后续的MOVE,UP才会传递到该View。

五.ListView的问题

想给ListView实现左右滑动翻页的功能,正常是想着使用ViewFillper,但是不用ViewFlipper动态改变adapter的内容再刷新ListView的话应该如何实现呢。有下面的想法

1.给ListView定义一个手势对象gestureDector,重写它的onTouchEvent,在里面使用return gestureDector.onTouchEvent。gestureDector的手势监听器默认在onDown的时候返回false,所以一个DOWN的motionevent传过来,onTouchEvent返回false,根据上面的说法,dispatchTouchEvent也将返回false,后续motionevent将不会再传递到ListView,失败。

2.重写手势监听器的onDown返回值为true,这时可以实现左右翻页,但是ListView本身的onItemClickListener将没办法正常工作。失败。

3.重写dispatchTouchEvent,调用super.dispatchTouchEvent,然后始终返回true。重写onTouchEvent,首先调用gestureDector.onTouchEvent,如果返回为false,说明gestureDector.onTouchEvent没有处理该事件,我们的左右滑动也没有触发,那么return super.onTouchEvent处理,包括它的onItemClickListener等等都可以正常运行。不管super.onTouchEvent返回何值,因为dispatchTouchEvent返回了true,所以后续的动作都会传来。如果返回为true,说明gestureDector.onTouchEvent处理了左右滑动事件(前提是在手势监听器里面fling动作返回了true),此时return true。成功。

4.重写dispatchTouchEvent,一开始就调用gestureDector.onTouchEvent,然后同样的处理方式,最终保证能够return true。


https://www.xamrdz.com/mobile/4zb1939596.html

相关文章: