事件分发使用责任链设计模式
1、责任链设计模式个人总结重点
1)声明接口规则用于向下传递待处理对象
2)抽象出处理方法,是否对待处理对象进行处理
3)持有链表下游引用
4)实现接口方法:若自己不处理,则传递给下游处理
5)接口方法要返回是否拦截结果
当返回false时,说明自己和下游全都不处理;返回true时,说明自己处理了或可知下游链中有人处理了
6)通过设置持有对象完成责任链
2、Demo
Callback.java接口规则
//事件流转协议 public interface Callback<T> { //传递某个事件/obj boolean dispatch(T obj); }
Stub.java,抽象出处理和分发
public abstract class Stub<T> implements Callback<T> { private Callback next; //设置下游对象 public void setNext(Callback Callback) { this.next = Callback; } @Override public boolean dispatch(T obj) { //如果自己不处理,就分发给下游处理 boolean intercept = onIntercept(obj); if (!intercept && next != null) { //是否处理从链表下游返回 intercept = next.dispatch(obj); } return intercept; } //自己是否处理 abstract boolean onIntercept(T obj); }
三个实现类
public class Stub1 extends Stub<Integer> { @Override boolean onIntercept(Integer obj) { if (obj <= 6) { Log.i("stub","stub1 处理了"); return true; } return false; } }
public class Stub2 extends Stub<Integer> { @Override boolean onIntercept(Integer obj) { if (obj > 6 && obj <= 66) { Log.i("stub", "stub2 处理了"); return true; } return false; } }
public class Stub3 extends Stub<Integer> { @Override boolean onIntercept(Integer obj) { if (obj > 66 && obj <= 666) { Log.i("stub","stub3 处理了"); return true; } return false; } }
Stub1 stub1 = new Stub1(); Stub2 stub2 = new Stub2(); Stub3 stub3 = new Stub3(); //设置责任链 stub1.setNext(stub2); stub2.setNext(stub3); stub3.setNext(new Stub() { @Override boolean onIntercept(Object obj) { Log.i("stub", "无论如何由我顶住!"); return true; } }); stub1.dispatch(656);
最终打印
View的事件分发机制
view事件分发采用责任链设计模式,伪代码参考如下。
事件先由Activity-->window-->DecorView进入到顶级View(一般是个ViewGroup),
调用dispatchTouchEvent(MotionEvent ev)方法,在此方法中先调用onInterceptTouchEvent(MotionEvent ev)判断是否拦截,
若拦截事件就调用onTouchEvent(MotionEvent ev)处理事件,否则就调用子View的dispatchTouchEvent(MotionEvent ev),将事件传递给子view开始下一次循环
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; }
一、ViewGroup对点击事件的分发
1、ViewGroup的 dispatchTouchEvent(MotionEvent ev)
在分析代码前先了解两个变量
mFirstTouchTarget 当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值指向子元素
FLAG_DISALLOW_INTERCEPT,这个标记位通过requestDisallowInterceptTouchEvent()方法设置,一般用于子View中,用于干预父View的事件分发
1 当新事件序列到来时,进行一些重置操作 2 // Handle an initial down. 3 if (actionMasked == MotionEvent.ACTION_DOWN) { 4 // Throw away all previous state when starting a new touch gesture. 5 // The framework may have dropped the up or cancel event for the previous gesture 6 // due to an app switch, ANR, or some other state change. 7 cancelAndClearTouchTargets(ev); 8 resetTouchState(); 9 } 10 11 判断是否进行事件的拦截 12 // Check for interception. 13 final boolean intercepted; 14 if (actionMasked == MotionEvent.ACTION_DOWN 15 || mFirstTouchTarget != null) { 16 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 17 if (!disallowIntercept) { 18 intercepted = onInterceptTouchEvent(ev); 19 ev.setAction(action); // restore action in case it was changed 20 } else { 21 intercepted = false; 22 } 23 } else { 24 // There are no touch targets and this action is not an initial down 25 // so this view group continues to intercept touches. 26 intercepted = true; 27 }
1)当事件为ACTION_DOWN时,也就是一个新的事件序列到来时(事件序列由一个DOWN开始,中间N个MOVE,一个UP结束),会先进行一些重置操作
cancelAndClearTouchTargets(ev); 将会对mFirstTouchTarget进行置空
resetTouchState() 重置FLAG_DISALLOW_INTERCEPT
2)ViewGroup判断是否进行事件的拦截
由14行代码得出 actionMasked != MotionEvent.ACTION_DOWN && mFirstTouchTarget != null 时 intercepted = true;
当事件是MOVE、UP(不是一个新的事件序列)并且没有子View处理,说明ViewGroup自己已拦截了DOWN事件,onInterceptTouchEvent()不会被调用,ViewGroup直接拦截。
由此看出,ViewGroup一旦拦截了DOWN事件,那么整个事件序列都由ViewGroup拦截处理。
已知事件为DOWN时会进行清空操作,FLAG_DISALLOW_INTERCEPT已被重置,disallowIntercept == false,此时ViewGroup会调用onInterceptTouchEvent()进行是否拦截的判断
由此看出,每次DOWN事件到来时ViewGroup都会进行拦截判断
当 mFirstTouchTarget != null 时,ViewGroup正常情况下会判断是否要拦截事件,但如果子View设置了FLAG_DISALLOW_INTERCEPT(由上可知此时一定不是Down事件),disallowIntercept == true,ViewGroup不拦截事件,onInterceptTouchEvent()也不会被调用
通过设置FLAG_DISALLOW_INTERCEPT,可以让ViewGroup无法拦截除DOWN事件外的其他事件。(滑动冲突解决方案)
2、ViewGroup的 onInterceptTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) //来自鼠标,一般不会有这个操作 && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true; } return false; }
ev.isFromSource(InputDevice.SOURCE_MOUSE) 表示事件来自鼠标,一般不会由此操作,所以onInterceprTouchEvent()返回false
可知ViewGroup默认不拦截事件
由上知onInterceptTouchEvent()在DOWN事件来临时一定会被调用,如果返回true进行拦截,那么接下来的MOVE、UP事件也都会被拦截交给ViewGroup的onTouchEvent处理,此时onInterceptTouchEvent()并不会被调用
所以重写此方法处理事件时需要注意,它并不会每次都会被调用
3、确认不拦截后,ViewGroup具体怎样分发事件到子元素呢
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 ...... 3 if (!canceled && !intercepted) { 4 final View[] children = mChildren; 5 for (int i = childrenCount - 1; i >= 0; i--) { 6 final int childIndex = getAndVerifyPreorderedIndex( 7 childrenCount, i, customOrder); 8 final View child = getAndVerifyPreorderedView( 9 preorderedList, children, childIndex); 10 11 // If there is a view that has accessibility focus we want it 12 // to get the event first and if not handled we will perform a 13 // normal dispatch. We may do a double iteration but this is 14 // safer given the timeframe. 15 if (childWithAccessibilityFocus != null) { 16 if (childWithAccessibilityFocus != child) { 17 continue; 18 } 19 childWithAccessibilityFocus = null; 20 i = childrenCount - 1; 21 } 22 23 if (!canViewReceivePointerEvents(child) 24 || !isTransformedTouchPointInView(x, y, child, null)) { 25 ev.setTargetAccessibilityFocus(false); 26 continue; 27 } 28 29 newTouchTarget = getTouchTarget(child); 30 if (newTouchTarget != null) { 31 // Child is already receiving touch within its bounds. 32 // Give it the new pointer in addition to the ones it is handling. 33 newTouchTarget.pointerIdBits |= idBitsToAssign; 34 break; 35 } 36 37 resetCancelNextUpFlag(child); 38 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 39 // Child wants to receive touch within its bounds. 40 mLastTouchDownTime = ev.getDownTime(); 41 if (preorderedList != null) { 42 // childIndex points into presorted list, find original index 43 for (int j = 0; j < childrenCount; j++) { 44 if (children[childIndex] == mChildren[j]) { 45 mLastTouchDownIndex = j; 46 break; 47 } 48 } 49 } else { 50 mLastTouchDownIndex = childIndex; 51 } 52 mLastTouchDownX = ev.getX(); 53 mLastTouchDownY = ev.getY(); 54 newTouchTarget = addTouchTarget(child, idBitsToAssign); //完成对mFitstTouchEvent的赋值 55 alreadyDispatchedToNewTouchTarget = true; 56 break; 57 } 58 59 .... 60 } 61 ...... 62 }
首先遍历ViewGroup所有子元素,判断子元素是否能够接收到点击事件,接收条件:子元素在播动画或者是VISIBLE 并且 点击事件的坐标是否落在子元素的区域内。如代码23行 if 判断,子元素不满足条件continue开始下一轮判断
满足条件继续向下走,38行代码,在 dispatchTransformedTouchEvent() 中调用子元素的dispatchTouchEvent(MotionEvent ev),将事件分发给子元素。
若child.dispatchTouchEvent(transformedEvent) 返回true,表示子元素成功处理事件,在addTouchTarget中完成对mFirstTouchEvent的赋值然后跳出for循环。
1 /** 2 * Transforms a motion event into the coordinate space of a particular child view, 3 * filters out irrelevant pointer ids, and overrides its action if necessary. 4 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. 5 */ 6 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 7 View child, int desiredPointerIdBits) { 8 .... 9 10 // Perform any necessary transformations and dispatch. 11 if (child == null) { 12 handled = super.dispatchTouchEvent(transformedEvent); 13 } else { 14 final float offsetX = mScrollX - child.mLeft; 15 final float offsetY = mScrollY - child.mTop; 16 transformedEvent.offsetLocation(offsetX, offsetY); 17 if (! child.hasIdentityMatrix()) { 18 transformedEvent.transform(child.getInverseMatrix()); 19 } 20 //此时child不为null,将事件分发给子元素 21 handled = child.dispatchTouchEvent(transformedEvent); 22 } 23 .... 24 }
而如果遍历所有子元素都没人处理事件,存在两种情况: 1)viewGroup没有子元素 2)子元素处理了点击事件,但在dispatchTouchEvent() 中返回了false,这一般是子元素在onTouchEvent中返回了false。
在这两种情况下ViewGroup将自己处理点击事件: 调用dispatchTransformedTouchEvent(),但传入的child为null,由上段代码可知,当child为null时,调用的是super.dispatchTouchEvent(transformedEvent),此时事件交由View来处理
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ...... // 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); } .... }
二、View对点击事件的分发
1、dispatchTouchEvent(MotionEvent ev)
View(不包含ViewGroup)是一个单独的元素,它没有子元素无需向下传递事件,只能自己处理,所以它并没有onInterceptTouchEvent(MotionEvent ev)
17行代码,View首先判断有没有设置onTouchListener,如果设置了onTouchListener且onTouch()返回了true,则不再调用onTouchEvent(),但有个前提 view是ENABLE
1 /** 2 * Pass the touch screen motion event down to the target view, or this 3 * view if it is the target. 4 * 5 * @param event The motion event to be dispatched. 6 * @return True if the event was handled by the view, false otherwise. 7 */ 8 public boolean dispatchTouchEvent(MotionEvent event) { 9 ...... 10 11 if (onFilterTouchEventForSecurity(event)) { 12 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { 13 result = true; 14 } 15 //noinspection SimplifiableIfStatement 16 ListenerInfo li = mListenerInfo; 17 if (li != null && li.mOnTouchListener != null 18 && (mViewFlags & ENABLED_MASK) == ENABLED 19 && li.mOnTouchListener.onTouch(this, event)) { 20 result = true; 21 } 22 23 if (!result && onTouchEvent(event)) { 24 result = true; 25 } 26 } 27 28 ...... 29 return result; 30 }
2、View的OnTouchEvent()
View是否可以消费事件首先看它是否是可点击的(CLICKABLE、LONG_CLICKABLE任意一个为true)
18行代码,即使View被设置为DISAVBLED状态,只要clickable为true它仍是可以消费事件的,只是不会对事件进行处理。
21行代码,如果View设置了代理,事件将交由代理View的onTouchEvent执行,如果代理View成功处理事件,当前View将直接返回true表示消耗事件,若代理View没有处理事件,代码向下执行,由当前View继续处理事件,这个和onTouchListener机制很像
45行代码,只要view是可点击的,那么它就会消耗事件,即onTouchEvent()返回true;
在UP事件中,如果View设置了onClickListener,将会调用它的onClick方法。可见 onTouchListener > onTouchEvent > onClickListener
1 public boolean onTouchEvent(MotionEvent event) { 2 final float x = event.getX(); 3 final float y = event.getY(); 4 final int viewFlags = mViewFlags; 5 final int action = event.getAction(); 6 7 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE 8 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 9 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; 10 11 if ((viewFlags & ENABLED_MASK) == DISABLED) { 12 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { 13 setPressed(false); 14 } 15 mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; 16 // A disabled view that is clickable still consumes the touch 17 // events, it just doesn‘t respond to them. 18 return clickable; 19 } 20 if (mTouchDelegate != null) { 21 if (mTouchDelegate.onTouchEvent(event)) { 22 return true; 23 } 24 } 25 26 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { 27 switch (action) { 28 case MotionEvent.ACTION_UP: 29 .... 30 break; 31 32 case MotionEvent.ACTION_DOWN: 33 .... 34 break; 35 36 case MotionEvent.ACTION_CANCEL: 37 .... 38 break; 39 40 case MotionEvent.ACTION_MOVE: 41 .... 42 break; 43 } 44 45 return true; 46 } 47 48 return false; 49 }
如有错误,请指正,谢谢!
感谢《Android开发艺术探索》!
原文:https://www.cnblogs.com/mariana/p/11994389.html