Android事件分发机制浅析
来源:互联网 发布:淘宝上的汉斯格雅花洒 编辑:程序博客网 时间:2024/04/30 03:18
事件机制是Android中一个比较复杂且重要的知识点,比如你想自定义拦截事件,或者某系组件中嵌套了其他布局,往往会出现这样那样的事件冲突,坑爹啊!!事件主要涵盖onTouch,onClick,onTouchEvent,dispatchTouchEvent,onInterceptTouchEvent等等一系列事件,并且事件间还相互交互耦合,甚至有的事件还有返回值,一会true,一会false,什么情况下返回true,什么情况下返回false,为什么要有返回值,想想这些就感觉整个人都不好了。
但是(万恶的但是),该知识点还是必须要掌握的,知识的深度与广度决定了你走的远度,鉴于此我们就来捅一捅该知识点。
准备工作
俗话说工欲善其事必先利其器,为了看他的执行流程,我们还是先写个样例,打几个日志看看执行流程吧!
首先自定义一个外层布局的Layout,自定义Layout继承了LinearLayout,复写了相应的函数,在调用之前输入日志。如下:
public class Layout extends LinearLayout { public Layout(Context context) { super(context); init(); } public Layout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Layout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { //requestDisallowInterceptTouchEvent(false); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("Event", "Layout onInterceptTouchEvent " + MotionEvent.actionToString(ev.getAction())); return super.onInterceptTouchEvent(ev); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouchEvent(MotionEvent event) { Log.e("Event", "Layout onTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.onTouchEvent(event); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.e("Event", "Layout dispatchTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.dispatchTouchEvent(event); }}
我们还自定义了一个LogTextView,继承自TextView,也是为了输出日志,代码如下:
public class LogTextView extends TextView { public LogTextView(Context context) { super(context); } public LogTextView(Context context, AttributeSet attrs) { super(context, attrs); } public LogTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouchEvent(MotionEvent event) { Log.e("Event", "TextView onTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.onTouchEvent(event); } @Override public void setOnTouchListener(OnTouchListener l) { super.setOnTouchListener(l); } @Override public void setOnClickListener(OnClickListener l) { super.setOnClickListener(l); }}
接下来是布局文件了:
<?xml version="1.0" encoding="utf-8"?><com.sunny.event.wigdet.Layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <com.sunny.event.wigdet.LogTextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="40dp" android:background="#999999" android:padding="20dp" android:text="Hello World!"/> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:src="@mipmap/ic_launcher"/></com.sunny.event.wigdet.Layout>
布局中嵌套了两个view,一个TextView,一个ImageView。最后就是主界面了。
public class MainActivity extends AppCompatActivity { private LogTextView tv; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViews(); setViewListener(); } private void findViews() { tv = (LogTextView) findViewById(R.id.textView); imageView = (ImageView) findViewById(R.id.image); } private void setViewListener() { tv.setOnTouchListener(new View.OnTouchListener() { @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouch(View v, MotionEvent event) { Log.e("Event", "TextView onTouch " + MotionEvent.actionToString(event.getAction())); return true; } }); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("Event", "TextView onClick "); } }); imageView.setOnTouchListener(new View.OnTouchListener() { @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouch(View v, MotionEvent event) { Log.e("Event", "ImageView onTouch " + MotionEvent.actionToString(event.getAction())); return false; } }); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("Event", "ImageView onClick "); } }); }}
执行结果
round 1
TextView的onTouch返回为false,点击TextView,日志如下:
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_DOWN05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_MOVE05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_UP05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onClick
根据日志我们可以看到首先有一个ACTION_DOWN事件,执行的顺序是Layout的dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch要→onTouchEvent,之后的我帕金森发生了,产生了ACTION_MOVE事件,传递的顺序与Down是一致的,最后一个事件是UP事件,正常点击不滑动是不会产生MOVE事件的,在这个这个三个事件最后调用了TextView的onClick事件。
小结:
1 . 事件的传递顺序是先外层容器,之后再是具体的View。
2. onTouch事件先于onTouchEvent事件,onTouchEvent先于onClick事件
round 2
我们将TextView的onTouch事件返回true。重新执行。执行顺序如下:
05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP
从日志可以看出如果onTouch返回为true,执行顺序变成了如下:
首先还是ACTION_DOWN事件(Layout)dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch,ACTION_MOVE与ACTION_UP执行顺序同ACTION_DOWN,可以发现的是TextView的onTouchEvent事件没有了,并且onClick事件也没有了。
小结
1,onTouch事件的返回值为true会拦截onTouchEvent事件
2,onTouchEvent与onClick有关联
上面的两次执行中每次都调用了onInterceptTouchEvent事件,这个到底又是啥?我们去看看他的返回值是什么?
public boolean onInterceptTouchEvent(MotionEvent ev) { return false;}
可以看到默认返回false,注释长的吓人,那我们就来改写一下他的返回值,这个函数是ViewGroup才有的,说明与布局容器有关.
round 3
我们将Layout的onInterceptTouchEvent的返回值改为true。重新执行。执行顺序如下:
05-05 14:59:17.829 15157-15157/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN05-05 14:59:17.830 15157-15157/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN05-05 14:59:17.830 15157-15157/com.sunny.event E/Event: Layout onTouchEvent ACTION_DOWN
从日志可以发现,只有最外层的控件能够执行事件,TextView已经收不到任何事件。
小结
父控件onInterceptTouchEvent返回true会拦截子控件的事件
追根溯源
我们从代码的层面来看看他是怎么执行的,当屏幕接收到点击事件时会首先传递到Activity的dispatchTouchEvent:
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
在这里执行了三步,
1.第一告诉用户ACTION_DOWN,用户可以复写onUserInteraction来处理点击开始
2.调用了getWindow().superDispatchTouchEvent(ev),这里的getWindow得到是PhoneWindow对象,因此执行的PhoneWindow的superDispatchTouchEvent函数,
3.调用了Activity的onTouchEvent事件
PhoneWindow的superDispatchTouchEvent又调用了DecorView的superDispatchTouchEvent函数,每一个Activity都有一个PhoneWindow,每一个PhoneWindow都有一个DecorView,DecoView继承自FrameLayout,这里又调用了super.dispatchTouchEvent(event),FrameLayout里面是没有改函数的,所以最终执行的是ViewGroup的dispatchTouchEvent函数。
这里我们先穿插一点界面的知识,以我测试手机为例,DecorView中有两个child,分别是ViewStub和LinerLayout,LinerLayout中又包含了FrameLayout,FrameLayout中包含了一个ActionBarOverlayLayout,ActionBarOverlayLayout里又包含了两个child,分别是ActionBarContainer与ContentFrameLayout。
接下来我们去看看ViewGroup的dispatchTouchEvent函数:
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { ......... 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) { 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); 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; } ............ // 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; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); ....... if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled;}
我们只看最重要的部分
1: 事件为ACTION_DOWN时,执行了cancelAndClearTouchTargets函数,该函数主要清除上一次点击传递的路径,之后执行了resetTouchState,重置了touch状态,其中执行了 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;就是拦截状态为false,这个与requestDisallowInterceptTouchEvent函数相关。
2: 获取intercepted的值,首先判断了disallowIntercept状态,是否拦截子控件的事件执行。从代码可以看到当disallowIntercept为false时,该状态主要取决于onInterceptTouchEvent函数的返回值,这就是前面我们拦截的函数,如果为true,这时intercepted为true标识拦截。
3: 接着判断了!canceled && !intercepted的值,canceled这里为false,如果intercepted为false,则会进入判断条件,这里假设不拦截,进入后继续判断如果是ACTION_DOWN事件,则会继续进入判断,遍历所有子控件,isTransformedTouchPointInView会判断当前点击区域是否在控件内,如果不在则遍历下一个,之后调用dispatchTransformedTouchEvent函数。最后在调用addTouchTarget函数,将当前选中的控件,挂载到当前点击目标链表。alreadyDispatchedToNewTouchTarget赋值为true。
接着判断mFirstTouchTarget是否为空,经过上一步的addTouchTarget的执行,这里mFirstTouchTarget不为空。第一个事件alreadyDispatchedToNewTouchTarget为true,且target == newTouchTarget,因此handled值为true,如果是后续的事件,则会进入dispatchTransformedTouchEvent中。
我们接着看看第三部中的dispatchTransformedTouchEvent函数:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; ....... final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } ....... // Done. transformedEvent.recycle(); return handled;}
这里会进入到第10行,且传递过来的child不为空,因此会继续执行child.dispatchTouchEvent,这里继续执行ViewGroup的dispatchTouchEvent,一直递归执行,直到真正接受点击的控件,到最后child会为空,这里要么是一个View控件,要么是未包含任何子控件的ViewGroup,这时这里会执行View的dispatchTouchEvent。
从上述执行逻辑可以直到,先从DecorView一直递归到Layout,最后再到TextView,这里我们去看看View的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent event) { 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;}
首先会停止掉嵌套滑动,之后先判断了ListenerInfo不为空,这里只要是设置了onTouch,onKey,onHover,onDrag等等中的任何一个这里就不为空,具体可以去看看ListenerInfo包含的listenerInfo类型。其次判断了mOnTouchListener不为空,只要设置了onTouchListener这里就不为空,再之后判断了该控件是否是enabled,一般都会enabled,可以代码设置为false,再之后调用了mOnTouchListener的onTouch事件,这里就是外面传进来的onTouchListener,从这里可以看到无论onTouch返回任何值,onTouch事件都会执行,但是如果返回为true,则会导致result为true,!result && onTouchEvent(event)因为短路,不会执行到onTouchEvent事件。
小结
1:onTouch返回为true导致onTouchEvent不能执行
2:如果enable为false,因为短路onTouch不会执行
到此还没有看到任何onClick事件的执行,我们继续去看看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 & 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) { // 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 && !mIgnoreNextUpEvent) { // 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(); } mIgnoreNextUpEvent = false; 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(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; 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;}
我们首先看ACTION_DOWN事件,这里主要看checkForLongClick,CheckForTap中也调用了该函数,这里就是添加一个长按事件,如果达到长按标准且长按listener不为空,则执行长按事件,接着我们看ACTION_UP,这里看到如果不是长按事件,则调用了performClick,performClick里面执行了onClick事件。
小结
1:onClick事件与onLongClick事件是在onTouchEvent中执行的
2:如果执行了长按事件则onClick不执行
3:就api 23代码,长按的时间间隔为500毫秒
上面解析了intercepted为false的情况,那intercepted为true,它到底是怎么拦截的?
如果intercepted为true,则!canceled && !intercepted为false,不能进入该判断,mFirstTouchTarget为空,会继续执行如下分支:
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);}
这里第三参数传递的child为null,因此就会执行该控件onTouch与onTouchEvent函数,不会继续递归传递,因此也就拦截了子控件的执行。
总结
- 事件接收先从父控件到子控件,如果父控件onInterceptTouchEvent为true,则表示拦截事件。
- dispatchTouchEvent的ACTION_DOWN事件中,会清除上一次的点击目标列表,且重置disallowIntercept状态为false,表示拦截,但是真正的拦截状态还是靠onInterceptTouchEvent函数的返回值决定。
- 如果为自定义控件,自定义控件与父控件有事件冲突,比如说滑动等,还需要重写onInterceptTouchEvent。
- 如果onLongClick执行,api 23 默认时间为500毫秒,则onClick不执行。
- 如果onTouch事件返回为true,则会拦截onTouchEvent事件,onClick,onLongClick事件均不在执行。
- Android事件分发机制浅析
- Android事件分发机制浅析
- 浅析android事件分发机制
- Android View的事件分发机制浅析
- 浅析Android事件分发
- Android滑动冲突与事件分发机制浅析
- android事件分发机制
- Android事件分发机制
- Android 事件分发机制
- Android事件分发机制
- Android 事件分发机制
- Android 事件分发机制
- android 事件分发机制
- Android事件分发机制
- android 事件分发机制
- android事件分发机制
- Android 事件分发机制
- android事件分发机制
- c++第5次实验-项目1-数组分离
- RxJava操作符(六)Utility
- “内存泄露”问题的分析和解决方法
- APK程序的运行过程
- 使用mplayer播放音乐时警告“mplayer can't open audio device /dev/dsp”
- Android事件分发机制浅析
- 面向切面编程二
- jQuery 向 servlet 发送和接受JSON数据。
- 循环名标签+break、continue控制内外层循环结构
- HI3520D在内核中复用UART2,UART3管脚
- JAVA中常用集合
- SubString
- RxJava操作符(七)Conditional and Boolean
- Android之Loader加载功能