美文网首页
Android中view的onTouchEvent方法源码分析

Android中view的onTouchEvent方法源码分析

作者: Corrine_Shao | 来源:发表于2018-05-22 14:41 被阅读149次

    上篇分析过了view的dispatchTouchEvent的源码的具体流程。
    首先介绍下view被点击的三个状态

    1、PrePress (姑且叫为预按),这个状态是我们的view被加入到一个scrolling view中才会存在。具体的意义是,举个简单的例子,当我们将手放在listView的一item上的时候,由于当前还不知道用户是要点击item还是想滑动listview,所以先将view设为PrePress状态;

    2、Press状态,用户将手放到view上面,如果不是1的状态,就里面设置为Press状态。那么先进入了PrePress,那么将会触发一个检测,也即CheckForTap(),默认时间是100ms,如果超过了100ms,将由PrePress进入到Press状态;

    3、LongPress状态,这个状态由Press状态过度过来,如果用户在一个view上停留超过一定的时间(默认为500ms),将进入该状态。

    话不多说,现在分析一下源码

     /**  实施此方法来处理触摸屏幕动作事件。
         * Implement this method to handle touch screen motion events.
         * <p>
         * If this method is used to detect click actions, it is recommended that
         * the actions be performed by implementing and calling
         * {@link #performClick()}. This will ensure consistent system behavior,
         * including:
         * <ul>
         * <li>obeying click sound preferences
         * <li>dispatching OnClickListener calls
         * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
         * accessibility features are enabled
         * </ul>
         *
         * @param event The motion event.
         * @return True if the event was handled, false otherwise.
         */
        public boolean onTouchEvent(MotionEvent event) {
      // 获取动作点击屏幕的位置坐标
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
      // 获取手势动作
    
    //如果当前View是一个DISABLED状态,且当前View是一个可点击或者是可长按的状态    则clickable返回true。表示当前事件在此消耗且不做处理
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
     //如果当前View状态为DISABLED
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
    //如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                }
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                return clickable;
            }
    
     // 如果设置了触摸代理
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
     //就交给mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
                    return true;
                }
            }
    
    //如果当前View的状态是可点击或者是可长按的,就对事件流进行细节处理
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
    //如果是抬起的手势
                    case MotionEvent.ACTION_UP:
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        if ((viewFlags & TOOLTIP) == TOOLTIP) {
                            handleTooltipUp();
                        }
    //清除各种状态
                        if (!clickable) {
                            removeTapCallback();
                            removeLongPressCallback();
                            mInContextButtonPress = false;
                            mHasPerformedLongPress = false;
                            mIgnoreNextUpEvent = false;
                            break;
                        }
    //prepressed指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态
    //大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态
                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
     //如果是pressed状态或者是prepressed状态,才进行处理   
                        if ((mPrivateFlags & PFLAG_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();
                            }
     //在释放之前给用户显示View的prepressed的状态,状态需要改变为PRESSED,并且需要将背景变为按下的状态为了让用户感知到
                            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.
                                setPressed(true, x, y);
                            }
      // 是否处理过长按操作了,如果是,则直接返回   
                            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // This is a tap, so remove the longpress check
      //如果不是长按的话,仅仅是一个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)方式调用失败,则直接调用
                                // If the post failed, unpress right now
                                mUnsetPressedState.run();
                            }
    //移除Tap的回调 重置View的状态
                            removeTapCallback();
                        }
                        mIgnoreNextUpEvent = false;
                        break;
    
    //如果是按下的手势
                    case MotionEvent.ACTION_DOWN:
                        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                            mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                        }
    //在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
                        mHasPerformedLongPress = false;
    
                        if (!clickable) {
    //仅在View支持长按时执行有效,否则直接退出方法
                            checkForLongClick(0, x, y);
                            break;
                        }
    
    //这个performButtonActionOnTouchDown(event) 一般的设备都是返回false.因为目前的实现中,它是处理如鼠标的右键的.(如果此View响应或者其父View响应右键菜单,那么就此事件就被消耗掉了.)
                        if (performButtonActionOnTouchDown(event)) {
                            break;
                        }
    
      // 判断当前view是否是在滚动器当中
                        // Walk up the hierarchy to determine if we're inside a scrolling container.
                        boolean isInScrollingContainer = isInScrollingContainer();
    
                        // For views inside a scrolling container, delay the pressed feedback for
                        // a short period in case this is a scroll.
                        if (isInScrollingContainer) {
                            mPrivateFlags |= PFLAG_PREPRESSED;
     //将view的状态变为PREPRESSED,检测是Tap还是长按事件
                            if (mPendingCheckForTap == null) {
                                mPendingCheckForTap = new CheckForTap();
                            }
    //如果是在滚动器当中,在滚动器当中的话延迟返回事件,延迟时间为 ViewConfiguration.getTapTimeout()=100毫秒
    //在给定的tapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动.
                            mPendingCheckForTap.x = event.getX();
                            mPendingCheckForTap.y = event.getY();
                            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                        } else {
                            // Not inside a scrolling container, so show the feedback right away
                            setPressed(true, x, y);
                            checkForLongClick(0, x, y);
                        }
                        break;
    
     //接收到系统发出的ACTION_CANCLE事件时,重置状态, 将所有的状态设置为最初始
                    case MotionEvent.ACTION_CANCEL:
                        if (clickable) {
                            setPressed(false);
                        }
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        break;
    
    //如果是移动的手势
                    case MotionEvent.ACTION_MOVE:
                        if (clickable) {
    //将实时位置传递给背景(前景)图片
                            drawableHotspotChanged(x, y);
                        }
    
                        // Be lenient about moving outside of buttons
    // 判断当前滑动事件是否还在当前view当中,不是就执行下面的方法
                        if (!pointInView(x, y, mTouchSlop)) {
                            // Outside button
                            // Remove any future long press/tap checks
    // 移除PREPRESSED状态和对应回调
                            removeTapCallback();
                            removeLongPressCallback();
                            if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
     // 是PRESSED就移除长按检测,并移除PRESSED状态
                                setPressed(false);
                            }
                            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        }
                        break;
                }
    
                return true;
            }
    
            return false;
        }
    
    

    相关文章

      网友评论

          本文标题:Android中view的onTouchEvent方法源码分析

          本文链接:https://www.haomeiwen.com/subject/nbymrftx.html