android事件之onInterceptTouchEvent,dispatchTouchEvent,onTouchEvent,requestDisallowInterceptTouchEvent

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kongbaidepao/article/details/47342937

android 的这个事件的分发传递,处理的解决方式,
实质应该是 java设计模式里面的 责任链模式了。

在这里,想用最少的话,最通俗易懂的方式记录

  1. View的方法
// 事件分发,默认返回false 
public boolean dispatchTouchEvent(MotionEvent event) 

// 事件处理,默认返回false 
public boolean onTouchEvent(MotionEvent event) 
  1. ViewGroup的方法
// 事件分发,默认返回false 
public boolean dispatchTouchEvent(MotionEvent event) 

// 事件处理,默认返回false 
public boolean onTouchEvent(MotionEvent event)  

// 拦截默认,默认返回false
// 返回true 就不会往下传递事件,自己onTouchEvent处理
// 返回false 向下传递事件
public boolean onInterceptTouchEvent(MotionEvent ev)

当触发一个事件,父布局会优先得到这个事件进行分发,也就是一般的

单独View (如果设置了OnTouchListener)

dispatchEvent–setOnTouchListener–onTouchEvent

单独看ViewGroup (如果设置了OnTouchListener)

dispatchTouchEvent –onInterceptTouchEvent–setOnTouchListener– onTouchEvent

3.ViewGroup 嵌套View
也就是我们平常最有用最关心的
前面说了底层的View能够接收到这次的事件有一个前提条件:在父层级允许的情况下。假设不改变父层级的dispatch方法,在系统调用底层onTouchEvent之前会先调用父View的onInterceptTouchEvent方法判断,父层View是不是要截获本次touch事件之后的action。
所以下面的这个东西一定要分清楚,这个还是很重要的,很多人很模糊,或者说很ran

事件的分发上的执行顺序:
(父)dispatchTouchEvent

(父)onInterceptTouchEvent

(子)dispatchEvent

事件的处理执行顺序
(子)onTouchEvent

(父)onTouchEvent

4 如果父View 和 子View都设置的点击事件相应的问题了

其实是都可以响应的,不多说,ListView中的adapter item布局都写的多了。

5 requestDisallowInterceptTouchEvent

requestDisallowInterceptTouchEvent 是ViewGroup类中的一个公用方法
Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent).
This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.

实际的应用中,可以在子view的ontouch事件中注入父ViewGroup的实例,并调用requestDisallowInterceptTouchEvent去阻止父view拦截点击事件
原来listView 第一行, 嵌套ViewPager ,其他行嵌套别的中就需要用到这个

需要在子布局中操作,父布局不拦截自身事件

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //让父类不拦截触摸事件就可以了。
        this.getParent().requestDisallowInterceptTouchEvent(true); 
        return super.dispatchTouchEvent(ev);

    }

下面有代码例子,看着也很详细。后面看到补充的
燕子的 http://blog.csdn.net/yanzi1225627/article/details/22592831

View的dispatchTouchEvent()函数

public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);  
        }  

        if (onFilterTouchEventForSecurity(event)) {  
            //noinspection SimplifiableIfStatement  
            ListenerInfo li = mListenerInfo;  

        //在这里会进行  onTouch 等的判断,如果此条件成功 就不往下走了
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
                    && li.mOnTouchListener.onTouch(this, event)) {  
                return true;  
            }  

            //如果上面条件不成立,继续往下执行 onTouchEvent
            if (onTouchEvent(event)) {  
                return true;  
            }  
        }  

        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
        }  
        return false;  
} 


public boolean onTouchEvent (){

 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)) {  

                     //这里执行了,我们注册的 onclick事件 
                                     performClick();
                                }  
                            }  
                        }
                   ........
            }


  public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

        ListenerInfo li = mListenerInfo;  
        if (li != null && li.mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
             li.mOnClickListener.onClick(this);  
            return true;  
        }  

        return false;  
    }

总结:

1、事件入口是dispatchTouchEvent(),它会先执行注册的onTouch监听,如果一切顺利的话,接着执行onTouchEvent,在onTouchEvent里会执行onClick监听。


2、无论是dispatchTouchEvent还是onTouchEvent,如果返回true表示这个事件已经被消费、处理了,不再往下传了。

(1)在dispathTouchEvent的源码里可以看到,如果onTouchEvent返回了true,那么它也返回true。

(2)如果dispatchTouchEvent 在执行onTouch监听的时候,onTouch返回了true,那么它也返回true,这个事件提前被onTouch消费掉了。就不再执行onTouchEvent了,更别说onClick监听了。



3、我们通常在onTouch监听了设置图片一旦被触摸就改变它的背景、透明度之类的,这个onTouch表示事件的时机。而在onClick监听了去具体干某些事。

//复写button
public class TestButton extends Button {  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        switch(event.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "TestButton-onTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "TestButton-onTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  
        return super.onTouchEvent(event);  
    }  

    @Override  
    public boolean dispatchTouchEvent(MotionEvent event) {  
        switch(event.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "TestButton-dispatchTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "TestButton-dispatchTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  

        return super.dispatchTouchEvent(event);  
    }  

}  

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

Activity 的

public boolean dispatchTouchEvent(MotionEvent ev) {  
        // TODO Auto-generated method stub  
        switch(ev.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "MainActivity-dispatchTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "MainActivity-dispatchTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  
    case MotionEvent.ACTION_DOWN:  
        Log.i(tag, "MainActivity-onTouchEvent-ACTION_DOWN...");  
        break;  
    case MotionEvent.ACTION_UP:  
        Log.i(tag, "MainActivity-onTouchEvent-ACTION_UP...");  
        break;  
    default:break;  
    }  
    return super.onTouchEvent(event);  
}


//按钮注册 onclick
testBtn.setOnClickListener(new View.OnClickListener() {  

            @Override  
            public void onClick(View v) {  
                // TODO Auto-generated method stub  
                Log.i(tag, "testBtn---onClick...");  
            }  
        });  

//按钮注册 onTouch
testBtn.setOnTouchListener(new View.OnTouchListener() {  

    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  
    case MotionEvent.ACTION_DOWN:  
        Log.i(tag, "testBtn-onTouch-ACTION_DOWN...");  
        break;  
    case MotionEvent.ACTION_UP:  
        Log.i(tag, "testBtn-onTouch-ACTION_UP...");  
        break;  
    default:break;  

    }  
    return false;  
    }  
});

MainActivity-dispatchTouchEvent-ACTION_DOWN...

TestButton-dispatchTouchEvent-ACTION_DOWN...
testBtn-onTouch-ACTION_DOWN...
TestButton-onTouchEvent-ACTION_DOWN...

MainActivity-dispatchTouchEvent-ACTION_UP...

TestButton-dispatchTouchEvent-ACTION_UP...
testBtn-onTouch-ACTION_UP...
TestButton-onTouchEvent-ACTION_UP...
testBtn---onClick...

事件先由Activity的dispatchTouchEvent进行分发,然后TestButton的dispatchTouchEvent进行分发,接着执行onTouch监听,然后执行onTouchEvent。第二次UP动作的时候,在onTouchEvent里又执行了onClick监听。

如果我们想这个TestButton只能执行onTouch监听不能执行onClick监听,方法有很多。在onTouch监听里默认返回false改为true,如下:


@Override  
    public boolean onTouch(View v, MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  

    }  
    return treu;  
    }  

返回true 以后,表示 ontouch 消费此事件,不向西传递了 所以就不走 onTouchEvent了


public boolean dispatchTouchEvent(MotionEvent ev) {  
        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  
        }  

        boolean handled = false;  
        if (onFilterTouchEventForSecurity(ev)) {  
            final int action = ev.getAction();  
            final int actionMasked = action & MotionEvent.ACTION_MASK;  

            // Handle an initial down.  
            if (actionMasked == MotionEvent.ACTION_DOWN) {  
                // Throw away all previous state when starting a new touch gesture.  
                // The framework may have dropped the up or cancel event for the previous gesture  
                // due to an app switch, ANR, or some other state change.  
                cancelAndClearTouchTargets(ev);  
                resetTouchState();  
            }  

            // Check for interception.  
            final boolean intercepted;  
            if (actionMasked == MotionEvent.ACTION_DOWN  
                    || mFirstTouchTarget != null) {  
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
                if (!disallowIntercept) {  

            // 在这里
                    intercepted = onInterceptTouchEvent(ev);  // 默认返回false
                    ev.setAction(action); // restore action in case it was changed  
                } else {  

            //
                    intercepted = false;
                }


      } else {  
                // There are no touch targets and this action is not an initial down  
                // so this view group continues to intercept touches.  
                intercepted = true;  
            }  

            // Check for cancelation.  
            final boolean canceled = resetCancelNextUpFlag(this)  
                    || actionMasked == MotionEvent.ACTION_CANCEL;  

            // Update list of touch targets for pointer down, if needed.  
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;  
            TouchTarget newTouchTarget = null;  
            boolean alreadyDispatchedToNewTouchTarget = false;  

        //================================
              if (!canceled && !intercepted) {  
                if (actionMasked == MotionEvent.ACTION_DOWN  
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)  
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {  
                    final int actionIndex = ev.getActionIndex(); // always 0 for down  
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)  
                            : TouchTarget.ALL_POINTER_IDS;  

              ........
          . ......

        }

 MainActivity-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN... false
 TestButton-dispatchTouchEvent-ACTION_DOWN...
 testBtn-onTouch-ACTION_DOWN...
 TestButton-onTouchEvent-ACTION_DOWN...
---------------------------------------------------------------------------------------------------------------------------
 MainActivity-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-onInterceptTouchEvent-ACTION_UP...   false
 TestButton-dispatchTouchEvent-ACTION_UP...
 testBtn-onTouch-ACTION_UP...
 TestButton-onTouchEvent-ACTION_UP...
 testBtn---onClick...


2,如果将TestLinearlayout的 onInterceptTouchEvent (拦截) 改成return true,即不让孩子们知道。

 MainActivity-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN... true 自己处理

 testLinelayout-onTouch-ACTION_DOWN...
 TestLinearLayout-onTouchEvent-ACTION_DOWN...

 MainActivity-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-dispatchTouchEvent-ACTION_UP...
 testLinelayout-onTouch-ACTION_UP...
 TestLinearLayout-onTouchEvent-ACTION_UP...
 testLinelayout---onClick...
1、如果是自定义复合控件,如图片+文字,我再Activity里给你注册了onClick监听,期望点击它执行。那么最简单的方法就是将图片+文字的父布局,
也即让其容器ViewGroup的秘书将事件拦下,这样父亲就可以执行onClick了。这时候的父亲就像一个独立的孩子一样了(View),无官一身轻,
再也不用管它的孩子了,可以正常onClick onTouch.

2、如果希望一个View只onTouch而不onClick,在onTouch里return true就ok了。

3、dispatch是为了onTouch监听,onTouchEvent是为了onClick监听。

4、自定义布局时,一般情况下:
@Override
    public boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}  

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);
    我们可以复写,但是最后的super.***是万万不能少滴。如果少了,表示连dispatch*** onTouchEvent压根就不调用了,事件就此打住。

猜你喜欢

转载自blog.csdn.net/kongbaidepao/article/details/47342937