程序员人生 网站导航

自定义View系列教程06--详解View的Touch事件处理

栏目:综合技术时间:2016-07-05 14:16:39

自定义View系列教程01--经常使用工具介绍

自定义View系列教程02--onMeasure源码详实分析

自定义View系列教程03--onLayout源码详实分析

自定义View系列教程04--Draw源码分析及其实践

自定义View系列教程05--示例分析

自定义View系列教程06--详解View的Touch事件处理

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程08--滑动冲突的产生及其处理


先上图:


说在前面:

View的事件分发简单记忆方法::dispathTouchEvent----->onTouchEvent------->onClick

如上图,我把View的事件分发分为两大块:

第1块:在dispatchTouchEvent()方法中。

       1 首先判断当前的OnTouchListener是不是为null。

       2 判断当前的控件是不是是ENABLED状态。
       3 判断onTouch方法返回的是true还是false。
         如果以上3步:
 有任何1个步骤返回false。那末就调用onTouchEvent(onTouchEvent返回true,则dispathTouchEvent 返回true;返回false,则dispathTouchEvent 返回false。)

        所有步骤都返回true。不调用其他方法,dispathTouchEvent ()返回true;

第2块:在onTouchEvent方法中。

     1.当控件不可用时:

           当控件有点击事件,返回true,但不会调用onClick等点击事件

           当空间无点击事件,返回false

      2.当控件可用时:

           无点击事件,返回false

           有点击事件,就走Switch()判断,判断是move,down,up,cancle,最后返回true

           在up时,会调用preformClick()------>onClick()事件。

附录:

//iamgeView调用view上面setOnTouchListener方法。 imageView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //返回true,致使事件全部被响应 return false; } }); //给指定的iamgeView去设置1个点击事件 imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ... } }); //点击事件的处理规则,mOnClickListener甚么时候传递进来的?? public boolean performClick() { ... if (mOnClickListener != null) { .... mOnClickListener.onClick(this); ... } ... }<pre name="code" class="java"> //view中setOnClickListener方法 public void setOnClickListener(OnClickListener l) { //如果当前控件没有点击事件,设置1个点击事件 if (!isClickable()) { setClickable(true); } mOnClickListener = l; }


--------------------------------我是华丽的分割线-----------------------ok请看正文---------------------------------------------------------------------------------------

在之前的几篇文章中结合Andorid源码还有示例分析完了自定义View的3个阶段:measure,layout,draw。 在自定义View的进程中我们还常常需要处理View的Touch事件,这就触及到了大伙常说的Touch事件的分发。其实,这1部份还是有些复杂的,而且有的地方不是很好理解,特别是对刚上路的新司机来讲常常理不清楚,欲求不满,欲罢不能——想弄懂却又觉得难,想放弃又觉得舍不得。

好吧,我也经历过这些痛楚,感同身受。

所以,我们就从相对而言比较简单的View的Touch事件处理入手开始这部份知识的学习和总结。

滴滴,开车了,车门行将关闭。上车请刷卡,没卡的乘客请投币。


如果1个View(比如Button)接收到Touch,那末该Touch事件首先会传入到它的dispatchTouchEvent( )方法,所以我们从这里开始学习View对Touch事件的处理。

/** * 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 (event.isTargetAccessibilityFocus()) { if (!isAccessibilityFocusedViewOrHost()) { return false; } event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { 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); } if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
嗯哼,这段源码不长,除注释就剩下不到100行了。该方法的输入参数为event它表示Touch事件,这个很好理解;那末它的返回值有是甚么含义呢?该boolean值表示的是Touch事件是不是被消费。

在此,对该部份源码的核心部份和主要逻辑做1个梳理

第1步: 
调用TouchListener中的onTouch()处理Touch事件,请参见代码第31⑶2行

该if判断中1共包括了4个条件,必须同时满足时才表示Touch事件被消费

  1. li != null 
    ListenerInfo是View中的1个静态类,包括了几个Listener,比如TouchListener,FocusChangeListener,LayoutChangeListeners,ScrollChangeListener等等。1般情况下它均不为null,所以我们不用过量关注它。
  2. li.mOnTouchListener != null 
    mOnTouchListener是由View设置的,比如mButton.setOnTouchListener()。所以如果View设置了Touch监听那末,那末mOnTouchListener不空;反之,mOnTouchListener为null
  3. (mViewFlags & ENABLED_MASK) == ENABLED 
    当前View可用(ENABLED)。通常可调用view.setEnabled( )设置View是不是可用
  4. li.mOnTouchListener.onTouch(this, event) 
    这1点实际上是在li.mOnTouchListener != null的基础上继续判断。判断TouchListener的onTouch( )方法是不是消耗了Touch事件。返回值为true表示消费掉该事件,false表示未消费。

在这4个条件中,我们通常最关心的就是最后1个:TouchListener的onTouch()方法。假设这4个条件中的任意1个不满足,那末result仍为false;则进入下1步

第2步: 
调用View本身的onTouchEvent()处理Touch事件,请参见代码第36⑶8行

if (!result && onTouchEvent(event)) { result = true; }
嗯哼,看到了吧:如果在上1步中Touch事件被消费result为true,就不会履行这3行代码了。该处调用了onTouchEvent()若该方法返回值false那末dispatchTouchEvent()的返回值也为false;反之,若该方法返回值为true,那末dispatchTouchEvent()的返回值亦为true。 

既然onTouchEvent()这么重要,我们就接着看该方法的源码

public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); if (!focusTaken) { 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)) { mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } 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 { setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); if (!pointInView(x, y, mTouchSlop)) { removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
这段代码略微复杂1些,在此分析几个核心点。

  1. 当View为disable时对Touch的处理,请参见代码第7⑴6行。 
    若1个View是disable的,如果它是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE的就返回true,表示消耗掉了Touch事件。 
    但是请注意,该view所对应的ClickListener.onClick( )不会有任何的响应。即官方文档的描写:

    A disabled view that is clickable still consumes the touch events, it just doesn’t respond to them.

    若View虽然是disable的,但只要满足这3个条件中的1个,它就会消费掉Touch事件但不再回调view的onClick( )方法

  2. 处理ACTION_DOWN,ACTION_MOVE,ACTION_UP事件等,请参见代码第24⑴16行。 
    在此请注意在对ACTION_UP的处理时调用了performClick(),请参见代码第50行。

    public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
    在该方法中调用了view的mOnClickListener.onClick( ),请参见代码第6行。 

    嗯哼,看到了吧:我们平常见得很多的Click事件是在View的onTouchEvent( )中处理ACTION_UP时调用的。

  3. 返回onTouchEvent()方法的输出结果,请参见代码第118⑴21行。 
    在该处请特别注意: 
    如果View是enable的,只要该View满足CLICKABLE和LONG_CLICKABLE和CONTEXT_CLICKABLE这3者的任意1个(请参见代码第24⑵6行)不论当前的action是甚么,该onTouchEvent()返回的均是true(请参见代码第118行);而且会在ACTION_UP时处理click事件。 
    同理,如果这3个条件都不满足,该onTouchEvent()返回的是false。 
    也请注意1个细节: 
    View的clickable属性视不同的子View有所差异 
    比如:Button的clickable默许为true,但是TextView的clickable属性默许为false。 
    View的longClickable属性默许为false。 
    固然,我们可以通过代码修改这些默许的属性。 
    比如:setClickable()和setLongClickListener()可以改变View的CLICKABLE和LONG_CLICKABLE属性。 
    除此之外,通过设置监听器也可改变某些属性。 
    比如:setOnClickListener()会将View的CLICKABLE设置为true;setOnLongClickListener()会将View的LONG_CLICKABLE设置为true。

第3步: 
返回Touch事件是不是被消费,请参见代码第52行

以上就为View对Touch事件的主要步骤。 
在此我画了1个简单的流程图,现结合该图和刚才的源码分析对View的Touch事件处理流程做1个总结。

这里写图片描述

  1. View处理Touch事件的整体流程 
    dispatchTouchEvent()—>onTouch()—>onTouchEvent()—>onClick() 
    Touch事件最早传入dispatchTouchEvent()中;如果该View存在TouchListener那末会调用该监听器中的onTouch()。在此以后如果Touch事件未被消费,则会履行到View的onTouchEvent()方法,在该方法中处理ACTION_UP事件时若该View存在ClickListener则会调用该监听器中的onClick()
  2. onTouch()与onTouchEvent()和click3者的区分和联系 
    2.1 onTouch()与onTouchEvent()都是处理触摸事件的API 
    2.2 onTouch()属于TouchListener接口中的方法,是View暴露给用户的接口便于处理触摸事件,而onTouchEvent()是Android系统本身对Touch处理的实现 
    2.3 先调用onTouch()后调用onTouchEvent()。而且只有当onTouch()未消费Touch事件才有可能调用到onTouchEvent()。即onTouch()的优先级比onTouchEvent()的优先级更高。 
    2.4 在onTouchEvent()中处理ACTION_UP时会利用ClickListener履行Click事件。所以Touch的处理是优先于Click的 
    2.5 简单地说3者履行顺序为:onTouch()–>onTouchEvent()–>onClick()
  3. View没有事件的拦截(onInterceptTouchEvent( )),ViewGroup才有,请勿混淆 

关于View对Touch事件的处理就分析到此。

滴滴,到站了,下车的乘客们请往后门走。

原文链接:http://blog.csdn.net/lfdfhl/article/details/51559847



------分隔线----------------------------
------分隔线----------------------------

最新技术推荐