事件分发系列—View中的dispatchTouchEvent和onTouchEvent分析

dispatchTouchEvent

话不多说直接上源码

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     * 将屏幕的按压事件传递给目标view,或者当前view即目标view
     *
     * @param event The motion event to be dispatched.
     * 需要分发的事件
     *
     * @return True if the event was handled by the view, false otherwise.
     * 如果返回true表示这个事件被这个view处理了,否则反
     */
    public boolean dispatchTouchEvent(MotionEvent event) {

        //系统调试分析相关,没有影响
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        //过滤是不是能够传递这个touch事件
        if (onFilterTouchEventForSecurity(event)) {

            //首先判断我们在使用该view的时候是否有实现OnTouchListener,如果有实现就判断当前的view
             //状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件,则
             //返回true,这个事件在此处理了。
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            //如果没有在代码里面setOnTouchListener的话,就判断View自身的onTouchEvent方法有没有
            //处理,没有处理最后返回false,处理了返回true;
            if (onTouchEvent(event)) {
                return true;
            }

        }
        //系统调试分析相关,没有影响
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        //如果即没有setOnTouchListener,也没有在onTouchEvent中处理,就返回false
        return false;

    }

从上面的源码可以看出:在View的分发事件方法dispatchTouchEvent中,处理分发的顺序是实现OnTouchListener的onTouch(),之后是当前View的onTouchEvent(event)方法。

onTouchEvent

onTouchEvent的源码有点长,所以要沉下心来仔细阅读。事件消耗和事件处理都是返回true,事件消耗就相当于占坑不拉屎,虽然有点恶心哈,事件处理当然就是占坑拉屎。

在Android的触摸消息中,已经实现了三种监测,它们分别是

1)pre-pressed:对应的语义是用户轻触(tap)了屏幕

2)pressed:对应的语义是用户点击(press)了屏幕

3)long pressed:对应的语义是用户长按(long press)了屏幕

下图是触摸消息随时间变化的时间轴示意图:

相关引用来自> http://www.linuxidc.com/Linux/2012-08/67979.htm



了解onTouchEvent就现需要了解的方法和类

  • CheckForTap类

    该类实现了Runnable接口,在run函数中设置触摸标识,并刷新Drawable的状态,同时用于发送一个检测长按事件的异步延迟消息,代码如下:

private final class CheckForTap implements Runnable {
    public void run() {
        // 进入该函数,说明已经过了ViewConfiguration.getTapTimeout()时间,
        // 即pre-pressed状态结束,宣告触摸进入pressed状态
        mPrivateFlags &= ~PREPRESSED;
        mPrivateFlags |= PRESSED;
        refreshDrawableState(); // 刷新控件的背景Drawable
        // 如果长按检测没有被去使能,则发送一个检测长按事件的异步延迟消息
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            postCheckForLongClick(ViewConfiguration.getTapTimeout());
        }
    }
}  

private void postCheckForLongClick(int delayOffset) {
    mHasPerformedLongPress = false;  

    // 实例化CheckForLongPress对象
    if (mPendingCheckForLongPress == null) {
        mPendingCheckForLongPress = new CheckForLongPress();
    }
    mPendingCheckForLongPress.rememberWindowAttachCount();
    // 调用PostDelayed函数发送长按事件的异步延迟消息
    postDelayed(mPendingCheckForLongPress,
            ViewConfiguration.getLongPressTimeout() - delayOffset);
}  
  • CheckForLongPress类

    该类定义了长按操作发生时的响应处理,同样实现了Runnable接口

class CheckForLongPress implements Runnable {  

    private int mOriginalWindowAttachCount;  

    public void run() {
        // 进入该函数,说明检测到了长按操作
        if (isPressed() && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
            if (performLongClick()) {
                mHasPerformedLongPress = true;
            }
        }
    }  

    public void rememberWindowAttachCount() {
        mOriginalWindowAttachCount = mWindowAttachCount;
    }
}  

public boolean performLongClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  

    boolean handled = false;
    if (mOnLongClickListener != null) {
        // 回调用户实现的长按操作监听函数(OnLongClickListener)
        handled = mOnLongClickListener.onLongClick(View.this);
    }
    if (!handled) {
        // 如果OnLongClickListener的onLongClick返回false
        // 则需要继续处理该长按事件,这里是显示上下文菜单
        handled = showContextMenu();
    }
    if (handled) {
        // 长按操作事件被处理了,此时应该给用户触觉上的反馈
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    }
    return handled;
}

了解完,我们来看看onTouchEvent的实现

   /**
     * Implement this method to handle touch screen motion events.
     * 如果需要处理屏幕产生的事件流需要实现这个方法
     *
     * @param event The motion event.
     *
     * @return True if the event was handled, false otherwise.
     * 如果返回true表示这个处理了这个事件,false则反
     */
    public boolean onTouchEvent(MotionEvent event) {

        //viewFLags用来记录当前View的状态
        final int viewFlags = mViewFlags;

        //如果当前View状态为DISABLED,如果不清楚DISABLED是一种什么状态那你应该用过
        //setEnabled(boolean enabled)这个方法,DISABLED就是ENABLED相反的状态。
        //DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
               //如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
            }
            //如果当前View是一个DISABLED状态,且当前View是一个可点击或者是可长按的状态则当前事件在
            //此消耗不做处理,返回true。
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

----------

        //TouchDelegate是一个事件处理逻辑封装的一个类,也就是说Touch事件处理被委托了,那么就交由
        //mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

----------

        //如果当前View的状态是可点击或者是可长按的,就对事件流进行细节处理
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    //PREPRESSED = 0x02000000
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    //如果是pressed状态或者是prepressed状态,才进行处理
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don‘t have it already and we should in
                        // touch mode.
                        //如果设定了获取焦点,那么调用requestFocus获得焦点
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            //在释放之前给用户显示View的prepressed的状态,状态需要改变为
                            //PRESSED,并且需要将背景变为按下的状态为了让用户感知到
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                       }
                        // 是否处理过长按操作了,如果是,则直接返回
                        if (!mHasPerformedLongPress) {
                            //如果不是长按的话,仅仅是一个Tap,所以移除长按的回调
                            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.
                                //UI子线程去执行click,为了让click事件开始的时候其他视觉发
                                //生变化不影响。
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //如果post消息失败,直接调用处理click事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

----------

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            //ViewConfiguration.getPressedStateDuration() 获得的是按下效
                            //果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125
                            //毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户
                            //感知到click的效果
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            //如果通过post(Runnable runnable)方式调用失败,则直接调用
                            mUnsetPressedState.run();
                        }
                        //移除Tap的回调 重置View的状态
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    //在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we‘re inside a scrolling container.
                    //判断是否在一个滚动的容器内
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // 如果父容器是一个可滚动的容器
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PREPRESSED;
                        //将view的状态变为PREPRESSED,检测是Tap还是长按事件
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        //直接将view状态转化为PRESSED,更新Drawable
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        //是否是长按事件的判断
                        checkForLongClick(0);
                    }
                    break;
                //接收到系统发出的ACTION_CANCLE事件时,重置状态
                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();

                    // 如果移动超出了按钮的范围
                    if (!pointInView(x, y, mTouchSlop)) {
                        // 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;
    }

对于onTouchEvent总的来说,首先受到事件首先判断当前View的状态是否为DISABLED,如果是则只需要简单的做一些状态的重置,不对事件做细节处理。如果不是DISABLED,就需要对事件细节进行处理,这时候又半路来个程咬金TouchDelegate,如果mTouchDelegate不为空,且返回了true,就表示该事件流有人给你代劳处理了,后面的分析View自己也不用做了。最后如果没人拦截处理,那就得View自己来。

下面开始是View自己处理事件流的逻辑过程描叙,即switch判断事件分支的处理:

  • ACTION_DOWN

    判断是否在触摸事件中执行按钮相关的动作,如果是,直接跳出,不是则继续判断当前View是否在一个可滑动的容器中,如果是则判断是否是一个点击tab事件,还是长按事件的检查,如果不是则直接转化状态为PRESSED并判断是否为长按事件。

  • ACTION_MOVE

    判断移动的点是否在当前View中,如果不在其中且当前状态为PRESSED则重置非PRESSED,且移除长按的回调。

  • ACTION_UP

    当抬起事件产生,首先判断View的状态是pressed状态或者是prepressed状态(也就是按过),才进行处理,如果是prepressed状态则变为pressed,并更新Drawable,然后判断在Down中的mHasPerformedLongPress标志,有没有变为true,也就是有没有产生执行长按事件,如果没有,则把这个事件流当做一个click事件做处理,也就是执行performClick中的代码,执行完click中的代码,最后重置View的状态和刷新Drawable。

  • ACTION_CANCLE

    当系统发送一个action_cancle的事件,则重置View的状态为非PRESSED,刷新Drawable的状态,且移除Tab的回调。

时间: 2024-10-24 13:47:30

事件分发系列—View中的dispatchTouchEvent和onTouchEvent分析的相关文章

【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑][下文简称(五),请先阅读完(五)再阅读本文],我们通过示例和log来分析了Android的事件分发机制.这些,我们只是看到了现象,如果要进一步了解事件分发机制,这是不够的,我们还需要透过现象看本质,去研究研究源码.本文将从源码(基

Android View 事件分发机制梳理

View初探 一直以来对View的事件分发机制很晕,今天就在这里梳理一下 MyView 首先继承View类,自定义一个MyView.并在初始化时打印View类是否可点击,这里从View点击事件分发的角度出发,所以不考虑绘制,测量相关方法的实现. public class MyView extends View { String TAG = "Activity"; public MyView(Context context) { super(context); init(); } pub

Android Touch事件分发机制学习

Android  事件分发机制 ViewGroup dispatchTouchEvent 返回true dispatchTouchEvent: Activity ACTION_DOWN MyrelativeLayout dispatchTouchEvent: ACTION_DOWN dispatchTouchEvent: Activity ACTION_UP MyrelativeLayout dispatchTouchEvent: ACTION_UP ViewGroup自己在dispatchTo

android ontouch事件分发机制

android中onclick,onlongclick,onfling,onscroll等事件都是由多个ontouch事件构成,一个完整的触屏事件必须包含1个ACTION_DOWN(按下),多个ACTION_MOVE(移动),1个ACTION_UP(放开)构成,touch事件分发就是这些事件在viewgroup和view之间轮转的过程. 1.viewgroup继承view,view中包含dispatchTouchEvent和onTouchEvent两个和事件分发直接相关两个方法,viewgrou

Android中的事件分发机制(下)——View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

【自定义View系列】04--谈谈事件分发

引言:这部分会分三个模块来讲,先讲View对Touch的处理,再讲ViewGroup的事件分发,最后讲如何解决滑动冲突. 我习惯通过在源码中添加注释来理解源码,以下是我提取出来几个重要方法,将不重要的部分删掉,并且添加了中文注释. 一.先从View讲起 如果一个View(比如Button)接收到Touch,那么该Touch事件首先会传入到它的dispatchTouchEvent( )方法,所以我们从这里开始学习View对Touch事件的处理. // 返回值表示Touch事件是否被该View消费

Android View的事件分发机制探索

概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解framework层的事件传递机制,就差不多了. 带着问题来思考整个事件分发过程. 1.为什么要有事件分发过程? 当Android设备的屏幕,接收到触摸的动作时,屏幕驱动把压力信号(包括压力大小,压力位置等)传递给系统底层,然后操作系统经过一系列的处理,然后把触摸事件一层一层的向上传递,最终事件会被准

Android查缺补漏(View篇)--事件分发机制源码分析

在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)--事件分发机制> ,先来看一下本篇的分析思路,一会儿会按照事件传递的顺序,针对以下几点进行源码分析: Activity对点击事件的分发过程 PhoneWindow是如何处理点击事件的 顶级View对点击事件的分发过程 View对点击事件的处理过程 Activity对点击事件的分发过程 通过上一篇博文中我们

【转】Android中的事件分发和处理

原文链接:http://www.apkbus.com/home.php?mod=space&uid=705730&do=blog&id=61207 上次跟大家分享了一下自定义View的一下要点,这次跟大家聊一下View的事件分发及处理,为什么主题都是View,因为作为一名初级应用层Android工程师,跟我打交道最多的莫过于各种各样的View,只有详细了解他们各自的习性,才能更好地跟他们沟通交流,做出自己想要的效果. 基础储备 View.MotionEvent 我们都能详细地说出A