简书链接:Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆 文章字数:1401,阅读全文大约需要5分钟
学习的问题 首先view触摸体系逻辑非常多,因此我这里只记录关键的信息。
疑问1: 2、 boolean onInterceptrEvent方法viewgroup有那么view有没有?? 3、 down和up什么区别?
总结 1、viewgroup 的dispatchTouchEvent()默认ACTION_DOWN返回true
3、哪个viewgroup被调用了requestDisallowInterceptTouchEvent表示哪个viewgroup就不会拦截触摸事件了,那个谁的父亲也会 被设置
1 2 3 4 5 6 7 8 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //如果设置requestDisallowInterceptTouchEvent 为true,则表示 禁止拦截触摸事件,而且通知viewgroup以及viewgroup父容器都禁止拦截 if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; }
4、外部设置的onTouchListener优先级要比自身的的onTouch优先级高如果返回true就不会再调用onTouchEvent 在View的dispatchTouchEvent方法可以看到下面这句话,如果外部设置的mOnTouchListener返回true 那么就不会再触发onTouchEvent
1 2 3 4 5 6 7 8 9 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; }
5、view的dispatchTouchEvent 逻辑比较少,主要处理自身的mOnTouchListener.onTouch触摸事件和自身的onTouchEvent回调 6、viewgroup的dispatchTouchEvent 逻辑比较多 ,在按下的时候如果是down 进行 onInterceptTouchEvent询问是否需要拦截,返回true表示拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; }
7、LinearLayout等常见布局都 没有复写dispatchTouchEvent()没有为什么,因为viewgroup已经写好了。 8、默认逻辑是viewgroup的onInterceptTouchEvent在收到down事件会直接返回true, 9、在viewgroup的dispatchTouchEvent中如果 被设置禁用拦截也就是disallowIntercept 为true,那么就不会触发onInterceptTouchEventonInterceptTouchEvent方法 10、view的 onTouchEvent默认是被view的dispatchTouchEvent方法 【mOnTouchListener为空或者mOnTouchListener返回 false时候】触发,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public boolean dispatchTouchEvent(MotionEvent event) { 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; } }
11、onInterceptTouchEvent或者mOnTouchListener.onTouch 返回false通常代表自己不拦截这个事件了. 12、onInterceptTouchEvent一旦返回true,那么之后是就不会再调用onInterceptTouchEvent,然后也不交给子view,而是触发自己的的mOnTouchListener onTouch或onTouchEvent
13、viewgroup的事件分发伪代码的摘要和view的触摸机制代码概要
viewgroup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public boolean dispatchTouchEvent(MotionEvent ev) { boolean intercepted = false; if (mDISALLOW_INTERCEIT) { intercepted = false; } else { intercepted = onInterceptTouchEvent(ev); } if (!intercepted) { final int childrenCount = mChildrenCount; for (int i = childrenCount - 1; i >= 0; i--) { preorderedList = null;//TODO 这里的意思不太理解 final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled(); int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); PseudocCodeView[] children = mChildren; final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex); newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { break; } if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent(); alreadyDispatchedToNewTouchTarget = true; break; } } } if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. boolean canceled = true; handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //也会调用 dispatchTransformedTouchEvent 暂时不太明白是做了什么交叉 } return intercepted; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) { final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } MotionEvent transformedEvent = MotionEvent.obtain(event); if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
view 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public boolean dispatchTouchEvent(MotionEvent event) { ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && ENABLE && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } return false; } protected boolean onTouchEvent(MotionEvent event) { // if(DISABLED){reutn false;} if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = true; if (PFLAG_PRESSED || prepressed) { if (!mHasPerformedLongPress) { performClick(); } } break; case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_MOVE: break; } return true; } return false; }
14、requestDisallowInterceptTouchEvent方法viewgroup声明的,设置为true 会导致viewgroup不会触发自己的onInterceptTouchEvent 15、childview只有在自己所在区域才会被触发 当childview不需要的时候会传递回调parent的onTouchEvent
1 2 3 4 5 6 7 8 9 :27:13.069 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start 05-02 18:27:13.069 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent false 05-02 18:27:13.070 14594-14594/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start 你好onTouchEvent false 你好dispatchTouchEvent end false 05-02 18:27:13.071 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onTouchEvent false 05-02 18:27:13.071 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endfalse
上面是全false代码,执行流程是viewgroup的
完整的点击事件代码
1 2 3 4 5 6 7 8 9 10 11 12 13 05-02 18:48:42.508 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start 05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent start onInterceptTouchEvent end false 05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start 05-02 18:48:42.509 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好onTouchEvent start 你好post runnable android.view.View$PerformClick 你好post runnable android.view.View$UnsetPressedState //执行post 你好onTouchEvent true 你好dispatchTouchEvent end true 05-02 18:48:42.509 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endtrue 05-02 18:48:42.510 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好performClick 05-02 18:48:42.511 25196-25196/cn.qssq666.interrview W/MainActivity: onClick 你好
17、同级的view默认情况下不管是被同级的遮盖,都会触发事件 也就是说一个点击区域的所有子view都会响应点击事件 但是优先级是谁在上面谁先得到响应事件
所谓上面,就是xml声明最靠后的就是上面的view.
18、 dispatchTouchEvent的根源 从viewrootimp的inputState传递 ->然后传递给phonewindow里面的 decoreview的上层view dispatchPointerEvent(decorview->(appcompatActivity的windowCallbackWrap)->activity-》->PhonewWindow->dispatchTransformedTouchEvent DecorView->ViewGroup…然后各种dispatchTransformedTouchEvent
19、最后贡献我写的完整 viewgroup到view的伪代码看完伪代码整个思路绝对会清晰很多,因为不会被多余代码干扰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 ``` package cn.qssq666.interrview; import android.support.annotation.NonNull; import android.view.MotionEvent; import android.view.ViewGroup; import java.util.ArrayList; /** * Created by qssq on 2018/5/2 [email protected] */ public class PseudocCodeViewGroup extends PseudocCodeView { /** * requestDisallowInterceptTouchEvent =true=mDISALLOW_INTERCEIT禁止拦截 */ private boolean mDISALLOW_INTERCEIT = false;//是否调用 ViewGroup viewGroup; private int mChildrenCount = 10; private TouchTarget newTouchTarget; private PseudocCodeView[] mChildren; private TouchTarget mFirstTouchTarget; private boolean alreadyDispatchedToNewTouchTarget; private ArrayList<PseudocCodeView> preorderedList; public boolean dispatchTouchEvent(MotionEvent ev) { boolean intercepted = false; if (mDISALLOW_INTERCEIT) { intercepted = false; } else { intercepted = onInterceptTouchEvent(ev); } boolean handled = false; if (!intercepted) { final int childrenCount = mChildrenCount; for (int i = childrenCount - 1; i >= 0; i--) { preorderedList = null;//TODO 这里的意思不太理解 final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled(); int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); PseudocCodeView[] children = mChildren; final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex); newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { break; } if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent(); alreadyDispatchedToNewTouchTarget = true; break; } } } if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. boolean canceled = true; handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget target = mFirstTouchTarget; TouchTarget predecessor = target; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target = next; continue; } } predecessor = target; target = next; } } return handled; } protected boolean onInterceptTouchEvent(MotionEvent ev) { return ev.getAction() == MotionEvent.ACTION_DOWN; } /** * 调用自己的child view * * @param event * @param cancel * @param child * @param desiredPointerIdBits * @return */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) { final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } MotionEvent transformedEvent = MotionEvent.obtain(event); if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; } private TouchTarget getTouchTarget(@NonNull PseudocCodeView child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null; } /** * 获取以及验证预览视图, * * @param preorderedList * @param children * @param childIndex * @return */ private static PseudocCodeView getAndVerifyPreorderedView(ArrayList<PseudocCodeView> preorderedList, PseudocCodeView[] children, int childIndex) { final PseudocCodeView child; if (preorderedList != null) { child = preorderedList.get(childIndex); if (child == null) { throw new RuntimeException("Invalid preorderedList contained null child at index " + childIndex); } } else { child = children[childIndex]; } return child; } private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) { final int childIndex; if (customOrder) { final int childIndex1 = getChildDrawingOrder(childrenCount, i); if (childIndex1 >= childrenCount) { throw new IndexOutOfBoundsException("getChildDrawingOrder() " + "returned invalid index " + childIndex1 + " (child count is " + childrenCount + ")"); } childIndex = childIndex1; } else { childIndex = i; } return childIndex; } private int getChildDrawingOrder(int childrenCount, int i) { return 0;//省略大量代码 } private static final class TouchTarget { private static final int MAX_RECYCLED = 32; private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones public PseudocCodeView child; public int pointerIdBits; public TouchTarget next; private TouchTarget() { } public static TouchTarget obtain(@NonNull PseudocCodeView child, int pointerIdBits) { if (child == null) { throw new IllegalArgumentException("child must be non-null"); } final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { target = new TouchTarget(); } else { target = sRecycleBin; sRecycleBin = target.next; sRecycledCount--; target.next = null; } } target.child = child; target.pointerIdBits = pointerIdBits; return target; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 ``` package cn.qssq666.interrview; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** * Created by qssq on 2018/5/2 [email protected] */ public class PseudocCodeView { private ListenerInfo mListenerInfo; private boolean ENABLE; private boolean result; private boolean CLICKABLE; private boolean LONG_CLICKABLE; private boolean CONTEXT_CLICKABLE; private boolean mHasPerformedLongPress; private boolean PFLAG_PRESSED; public boolean dispatchTouchEvent(MotionEvent event) { ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && ENABLE && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } return false; } protected boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); // if(DISABLED){reutn false;} if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = true; if (PFLAG_PRESSED || prepressed) { if (!mHasPerformedLongPress) { if (!mHasPerformedLongPress ) { removeLongPressCallback(); } performClick(); } } break; case MotionEvent.ACTION_DOWN: checkForLongClick(0, x, y); break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_MOVE: break; } return true; } return false; } public void performClick() { //点击点击事件 } CheckForLongPress mPendingCheckForLongPress=null; public void checkForLongClick(int delayOffset,float x,float y){ if(mPendingCheckForLongPress!=null) { mPendingCheckForLongPress = new CheckForLongPress(); } postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } private final class CheckForLongPress implements Runnable { private float mX; private float mY; @Override public void run() { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } /** * 长按事件触发 * @param mX * @param mY * @return */ private boolean performLongClick(float mX, float mY) { return false; } public void setAnchor(float x, float y) { } public void postDelayed(Runnable runnable,int delayTime){ //TODO } private void removeLongPressCallback() { if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } } private void removeCallbacks(Runnable mPendingCheckForLongPress) { //TOOD } static class ListenerInfo { /** * Listener used to dispatch focus change events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected View.OnFocusChangeListener mOnFocusChangeListener; /** * Listeners for layout change events. */ private ArrayList<View.OnLayoutChangeListener> mOnLayoutChangeListeners; protected View.OnScrollChangeListener mOnScrollChangeListener; /** * Listeners for attach events. */ private CopyOnWriteArrayList<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; /** * Listener used to dispatch click events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ public View.OnClickListener mOnClickListener; /** * Listener used to dispatch long click events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected View.OnLongClickListener mOnLongClickListener; /** * Listener used to dispatch context click events. This field should be made private, so it * is hidden from the SDK. * {@hide} */ protected View.OnContextClickListener mOnContextClickListener; /** * Listener used to build the context menu. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected View.OnCreateContextMenuListener mOnCreateContextMenuListener; private View.OnKeyListener mOnKeyListener; private PseudocCodeView.OnTouchListener mOnTouchListener; // private View.OnTouchListener mOnTouchListener; private View.OnHoverListener mOnHoverListener; private View.OnGenericMotionListener mOnGenericMotionListener; private View.OnDragListener mOnDragListener; private View.OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; } public interface OnTouchListener { /** * Called when a touch event is dispatched to a view. This allows listeners to * get a chance to respond before the target view. * * @param v The view the touch event has been dispatched to. * @param event The MotionEvent object containing full information about * the event. * @return True if the listener has consumed the event, false otherwise. */ boolean onTouch(PseudocCodeView v, MotionEvent event); } }
最后 实际上代码还有很多很多,很多细节,包括嵌套滑动的兼容并没有分析,从这里的伪代码也能够让你明白,点击事件是在那个触摸方法里面触发的,是如何解决点击事件不生效的.。我是菜鸟情迁,我为自己代言