本文主要是介绍Android源码解析之四:View事件分发机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
首先,我们明确几点:
1、事件的分发:父类将事件分发给子类:Activity—>ViewGroup—>View—>子View
2、事件的响应:子类响应父类传过来的事件:Activity<—ViewGroup<—View<—子View
3、事件的拦截:拦截父类分发下来的事件,就不会传给子类了。
具体方法如下:
事件分发:
public boolean dispatchTouchEvent(MotionEvent ev)
事件拦截:
public boolean onInterceptTouchEvent(MotionEvent ev)
事件响应:
public boolean onTouchEvent(MotionEvent ev)
控件对事件处理的优先级:onTouch>onTouchEvent>onClick(想要证明的话可以自己写个demo)底层事件执行顺序:ACTION_DOWN —>ACTION_MOVE—> ACTION_UP(ACTION_CANCEL,不太容易触发的事件,子视图执行ACTION_DOWN时父视图oninterceptTouchEvent 返回false,当子视图执行ACTION_MOVE时父视onInterceptTouchEvent 返回true,此时子视图会接收到ACTION_CANCEL)
Android 6.0(API 23) View触摸屏事件传递源码分析
好,接下来我们先来看View里面的dispatchTouchEvent()方法:
public boolean dispatchTouchEvent(MotionEvent event) {// If the event should be handled by accessibility focus first.if (event.isTargetAccessibilityFocus()) {// We don't have focus or no virtual descendant has it, do not handle the event.if (!isAccessibilityFocusedViewOrHost()) {return false;}// We have focus and got the event, then use normal event dispatch.event.setTargetAccessibilityFocus(false);}boolean result = false;if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}final int actionMasked = event.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {// Defensive cleanup for new gesturestopNestedScroll();}if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo 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;}
dispatchTouchEvent的代码有点长,咱们看重点就可以;重点是这个返回值result,前面几行代码都是设置一些标记和处理input与手势等传递;第9行代码中初始化result的值为false,到24行的if (onFilterTouchEventForSecurity(event))语句判断当前View是否没被遮住等,最主要的就是判断mListenerInfo不为空,mOnTouchListener不为null,并且view是enable的状态,然后 mOnTouchListener.onTouch(this, event)返回true,这四个条件如果都满足,直接return true ; 也就是下面的onTouchEvent(event)不会被执行了;,否则的话继续判断 onTouchEvent(event)的返回值,返回为true的时候也会直接返回result为true。
这里我们来看一下mListenerInfo对象在哪里获取的:
ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;}
还有他的mOnTouchListener对象,很明显不为空。
public void setOnTouchListener(OnTouchListener l) {getListenerInfo().mOnTouchListener = l;}
其实就是我们在Activity中设置的setOnTouchListener。也就是说:如果我们设置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不会被执行了,**
接下来我们来看一下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);}// 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)|| (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) {// 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 checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!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 nowmUnsetPressedState.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 awaysetPressed(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 buttonsif (!pointInView(x, y, mTouchSlop)) {// Outside buttonremoveTapCallback();if ((mPrivateFlags & PFLAG_PRESSED) != 0) {// Remove any future long press/tap checksremoveLongPressCallback();setPressed(false);}}break;}return true;}return false;}
此代码略长,挑重点,第15行代码中,如果我们的View可以点击或者可以长按,则,最终一定return true ;
if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {//...return true;}
接下来就是 switch (event.getAction())了,判断事件类型,DOWN,MOVE,UP等;今天喝了点小酒有点小困,就只分析MotionEvent.ACTION_UP了。ACTION_UP里面有这个,
如果mPerformClick如果mPerformClick为null,初始化一个实例,然后立即通过handler添加到消息队列尾部,如果添加失败则直接执行 performClick();添加成功,在mPerformClick的run方法中就是执行performClick();
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();}
}
终于执行了我们的click事件了,下面看一下performClick()方法:
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;}
久违了~我们的mOnClickListener ;同时也验证了OnClick事件在DOWN_UP之后执行了。
这篇关于Android源码解析之四:View事件分发机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!