Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆

学习的问题

首先view触摸体系逻辑非常多,因此我这里只记录关键的信息。

疑问1:

2、boolean onInterceptrEvent方法viewgroup有那么view有没有??
3、down和up什么区别?

总结

1、viewgroup 的dispatchTouchEvent()默认ACTION_DOWN返回true

3、哪个viewgroup被调用了requestDisallowInterceptTouchEvent表示哪个viewgroup就不会拦截触摸事件了,那个谁的父亲也会 被设置

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
ViewdispatchTouchEvent方法可以看到下面这句话,如果外部设置的mOnTouchListener返回true 那么就不会再触发onTouchEvent

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表示拦截

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时候】触发,

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 onTouchonTouchEvent

13、viewgroup的事件分发伪代码的摘要和view的触摸机制代码概要

viewgroup
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
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

: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的

完整的点击事件代码

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 你好
image.png
image.png

17、同级的view默认情况下不管是被同级的遮盖,都会触发事件 也就是说一个点击区域的所有子view都会响应点击事件
但是优先级是谁在上面谁先得到响应事件

image.png

所谓上面,就是xml声明最靠后的就是上面的view.

18、 dispatchTouchEvent的根源
从viewrootimp的inputState传递 ->然后传递给phonewindow里面的
decoreview的上层viewdispatchPointerEvent(decorview->(appcompatActivity的windowCallbackWrap)->activity-》->PhonewWindow->dispatchTransformedTouchEvent DecorView->ViewGroup...然后各种dispatchTransformedTouchEvent

19、最后贡献我写的完整 viewgroup到view的伪代码看完伪代码整个思路绝对会清晰很多,因为不会被多余代码干扰

viewgroup的伪代码

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 qssq666@foxmail.com
 */
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;
        }


    }
}

view的伪代码

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 qssq666@foxmail.com
 */
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);
    }
}
最后

实际上代码还有很多很多,很多细节,包括嵌套滑动的兼容并没有分析,从这里的伪代码也能够让你明白,点击事件是在那个触摸方法里面触发的,是如何解决点击事件不生效的.。我是菜鸟情迁,我为自己代言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值