Android应用开发原理之从View源码挖掘View中的事件传递机制

最近不怎么忙,就想着将自己开发中的一些知识点总结起来,方便以后自己在需要的时候进行查阅。
谈论起View我们都听熟悉的,因为一个Android上的界面组件,最初都是由View继承过来的。所以要想了解界面组件的一些事情,还得要从View说起。
大家常常都在重写onTouchevent,来实现一些自己想要的事件结果,所以我从onTouchevent开始追踪源码,最后追踪到了这个方法里面:

    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

看大概的意思,这个方法貌似是一个根据事件类型来分发事件处理器的方法。如果是触摸类事件,走的是上面这个方法:

 public boolean dispatchTouchEvent(MotionEvent event) {
        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)) {
            //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;
    }

大家有没有发现,你在用看一个长列表的时候,如果手在列表的开始向下快速拨动;列表会有一个惯性的向下滑动的效果,可是如果你在惯性滑动的过程中,手触摸上去,滑动立刻就会停止;不相信的可以试试,我相信你仔细看这段代码的逻辑;应该会找到问题的答案。

 public boolean onTouchEvent(MotionEvent event) {
        //分别获取手指所在的横坐标和纵坐标的位置
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // 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 & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_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 (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) {
                            // 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) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    break;

                //按下
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // 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;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        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);
                    }
                    break;
                 //取消时
                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;
               //滑动时
                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

我们一段一段分析:

 final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

``

定义局部内变量来记录触摸的位置;
定义一个viewFlags来记录mViewFlags;

 if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // 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));
        }

看意思来说应该是如果View被设置为不可触摸,那么就直接返回;
接着往下:

 if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

如果设置了mTouchDelegate对象,那么就将事件传递给mTouchDelegate去处理,这个可能是5.0以后新加的部分。
在继续往下:

 //按下事件
 case MotionEvent.ACTION_DOWN:
 //设置长按事件标志为false
     mHasPerformedLongPress = false;
     if (performButtonActionOnTouchDown(event)) {break;}

  //是不是包含在ViewGroup中
     boolean isInScrollingContainer = isInScrollingContainer();

    if (isInScrollingContainer) {
        mPrivateFlags |= PFLAG_PREPRESSED;
        if (mPendingCheckForTap == null) {
           mPendingCheckForTap = new CheckForTap();
           }
          mPendingCheckForTap.x = event.getX();
          mPendingCheckForTap.y = event.getY();
          //产生一个判断长按事件的方法
          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
   } else {
   //没有在一个ViewGroup中
   //设置按下的状态
         setPressed(true, x, y);
         //检测是否是长按事件
         checkForLongClick(0);
   }
 break;

可以看出,按下事件主要做了以下几件事情:
一:设置按下状态
二:检测长按事件
然后我们看move:

case MotionEvent.ACTION_MOVE:
          drawableHotspotChanged(x, y);

 // Be lenient about moving outside of buttons
          if (!pointInView(x, y, mTouchSlop)) {
              // Outside button
                removeTapCallback();
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                     // Remove any future long press/tap checks
                    removeLongPressCallback();

                    setPressed(false);
                }
          }
 break;

代码很短,可以看出,在Move里面,实现了以下逻辑:
一:检测触摸点是不是不在View的范围里面
二:如果不在,我们要移除长按检测,设置View为未触摸时的状态。
再看抬起的时候做了什么:
那么抬起的时候会做什么呢?
抬起时自然是处理在按下和移动中所设置的状态和事件了;注意在抬起的时候会根据条件判断是否产生Onclick事件;还有就是Click事件并不是立马执行,而是提交到队列中统一进行排队按顺序处理。
关于View就写到这里,总的来说,View在他自己的onTouchEvent中主要做一下几个事情:
一:设置状态,标志
二:判断长按和短按事件
三:对事件进行派发处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值