您的位置:首页 > 产品设计 > UI/UE

【UI】【View】View事件分发(一)

2016-06-29 11:05 435 查看
要了解view事件分发机制,首先要了解一些基础知识。

一、

事件基于先捕获然后冒泡的形式。

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

在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。

事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。

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

也就是事件分发、拦截和响应。(本文主要讲解事件分发)

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



事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,

如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,

最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok。

如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。

当然了,如果本层控件已经是Activity,那么事件将被系统消费或处理。

如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理

(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)。

 

事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev) 

如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;

如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。

如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。

 

 事件响应:public boolean onTouchEvent(MotionEvent ev)

如果onTouchEvent返回true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的冒泡。

如果onTouchEvent返回false,事件在onTouchEvent中处理后继续向上层View冒泡,且有上层View的onTouchEvent进行处理。

如果返回super.onTouchEvent(ev),则默认处理的逻辑和返回false时相同。

总结:从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,

只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,

但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。

二、
如下图,是一个简单的View树:



当我们发起动作,触摸事件是由Activity传给PhoneWindow再传到根mDercorView中。也就是从上图的树根,按照前序遍历,把触摸事件向下传。事件会先传到
ViewGroup1
,然后遍历它下面的三个子
View
,如果这三个子
View
都没有消费这个事件,就会尝试调用
ViewGruop1
消费这个事件。如果事件没有被消费,则
ViewGroup2
就会重复
ViewGroup1的进行步骤。如果
三个
ViewGroup
都没有消费掉事件,则事件会传到
ViewGroup0
去试着消费,如果也没有消费掉,最后就会传到
Activity
中进行消费。

====================================================================================================================================

以上描述了事件的传递过程,下面我们来看一下代码层级是如何进行的。

首先,一个触摸事件最先就是包装成一个
MotionEvent
给发送到
Activity
dispatchTounchEvent
了。那么我们先来看下这个方法:

(关于MotionEvent类请看另一篇博客,MotionEvent类详解

1)进入Activity的dispatchTouchEvent

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


这里调用了
Activity
所对应的
Window
superDispatchTouchEvent(ev)
方法来进行事件的分发。事实上也是调用
Window
的唯一实现类
PhoneWindow中的这个方法。
在这个方法中,直接调用了
mDecor.superDispatchTouchEvent
,代表事件终于
mDecor。
就是我们
Activity
setContent
中所设置的
View
的父容器中。

下面的几个知识点是对以上描述的总结:

MotionEvent
现在是传到
Activity
的顶级
View
的,我们的事件分发就是从这个顶级
View
向它的子
View
进行分发的。
事件分发就是把事件(
MotionEvent
) 按照先序遍历所有节点,直到找到一个
View
消费掉这个事件。所谓的消费这个事件,就是相应的
View
OntouchListener
返回
true
或者
OntouchEvent()
返回为
true

事件分发主要由三个函数控制,分别是
dispatchTouchEvent
分发事件,
onInterceptTouchEvent
拦截事件,
onTouchEvent
响应事件。

2)进入View的dispatchTouchEvent

[java] view
plain copy

/** 

     * Pass the touch screen motion event down to the target view, or this 

     * view if it is the target. 

     * 

     * @param event The motion event to be dispatched. 

     * @return True if the event was handled by the view, false otherwise. 

     */  

    public boolean dispatchTouchEvent(MotionEvent event) {  

        if (!onFilterTouchEventForSecurity(event)) {  

            return false;  

        }  

  

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  

                mOnTouchListener.onTouch(this, event)) {  

            return true;  

        }  

        return onTouchEvent(event);  

    }  

看13行,首先判断mOnTouchListener不为null,并且view是enable的状态,然后 mOnTouchListener.onTouch(this, event)返回true。如果这三个条件都满足,直接return true ,下面的onTouchEvent(event)不会被执行。

2)mOnTouchListener是什么:

[java] view
plain copy

/** 

  * Register a callback to be invoked when a touch event is sent to this view. 

  * @param l the touch listener to attach to this view 

  */  

 public void setOnTouchListener(OnTouchListener l) {  

     mOnTouchListener = l;  

 }  

其实是我们自己给控件设置的listener,用的方法是setOnTouchListener。

如果我们在OnTouchListener中重写onTouch方法,可以选择return true或者false.

那么如果return true,View自己的onTouchEvent方法不会被执行了。

已经解决一个常见的问题:View的onTouchListener和onTouchEvent的调用顺序关系。

3)View 的onTouchEvent

以下是源码:

[java] view
plain copy

/** 

     * Implement this method to handle touch screen motion events. 

     * 

     * @param event The motion event. 

     * @return True if the event was handled, false otherwise. 

     */  

    public boolean onTouchEvent(MotionEvent event) {  

        final int viewFlags = mViewFlags;  

  

        if ((viewFlags & ENABLED_MASK) == DISABLED) {  

            // A disabled view that is clickable still consumes the touch  

            // events, it just doesn't respond to them.  

            return (((viewFlags & CLICKABLE) == CLICKABLE ||  

                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  

        }  

  

        if (mTouchDelegate != null) {  

            if (mTouchDelegate.onTouchEvent(event)) {  

                return true;  

            }  

        }  

  

        if (((viewFlags & CLICKABLE) == CLICKABLE ||  

                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

            switch (event.getAction()) {  

                case MotionEvent.ACTION_UP:  

                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  

                        // take focus if we don't have it already and we should in  

                        // touch mode.  

                        boolean focusTaken = false;  

                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  

                            focusTaken = requestFocus();  

                        }  

  

                        if (!mHasPerformedLongPress) {  

                            // This is a tap, so remove the longpress check  

                            removeLongPressCallback();  

  

                            // Only perform take click actions if we were in the pressed state  

                            if (!focusTaken) {  

                                // Use a Runnable and post this rather than calling  

                                // performClick directly. This lets other visual state  

                                // of the view update before click actions start.  

                                if (mPerformClick == null) {  

                                    mPerformClick = new PerformClick();  

                                }  

                                if (!post(mPerformClick)) {  

                                    performClick();  

                                }  

                            }  

                        }  

  

                        if (mUnsetPressedState == null) {  

                            mUnsetPressedState = new UnsetPressedState();  

                        }  

  

                        if (prepressed) {  

                            mPrivateFlags |= PRESSED;  

                            refreshDrawableState();  

                            postDelayed(mUnsetPressedState,  

                                    ViewConfiguration.getPressedStateDuration());  

                        } else if (!post(mUnsetPressedState)) {  

                            // If the post failed, unpress right now  

                            mUnsetPressedState.run();  

                        }  

                        removeTapCallback();  

                    }  

                    break;  

  

                case MotionEvent.ACTION_DOWN:  

                    if (mPendingCheckForTap == null) {  

                        mPendingCheckForTap = new CheckForTap();  

                    }  

                    mPrivateFlags |= PREPRESSED;  

                    mHasPerformedLongPress = false;  

                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  

                    break;  

  

                case MotionEvent.ACTION_CANCEL:  

                    mPrivateFlags &= ~PRESSED;  

                    refreshDrawableState();  

                    removeTapCallback();  

                    break;  

  

                case MotionEvent.ACTION_MOVE:  

                    final int x = (int) event.getX();  

                    final int y = (int) event.getY();  

  

                    // Be lenient about moving outside of buttons  

                    int slop = mTouchSlop;  

                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||  

                            (y < 0 - slop) || (y >= getHeight() + slop)) {  

                        // Outside button  

                        removeTapCallback();  

                        if ((mPrivateFlags & PRESSED) != 0) {  

                            // Remove any future long press/tap checks  

                            removeLongPressCallback();  

  

                            // Need to switch from pressed to not pressed  

                            mPrivateFlags &= ~PRESSED;  

                            refreshDrawableState();  

                        }  

                    }  

                    break;  

            }  

            return true;  

        }  

  

        return false;
 



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息