美文网首页Android自定义View
View事件分发(四) - View事件分发(源码分析)

View事件分发(四) - View事件分发(源码分析)

作者: 世道无情 | 来源:发表于2019-01-27 16:21 被阅读12次

    1. 概述


    前两篇文章记录了View事件分发的一些理论基础,这篇文章主要 从 View的 dispatchTouchEvent 源码角度 分析下 View事件分发流程;

    下边通过一个示例代码来分析

    2. 示例如下


    创建 activity_main 布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button"
            android:layout_centerInParent="true"
            />
    
    </RelativeLayout>
    

    给Button设置 setOnClickListener、setOnTouchListener事件

    public class TextViewActivity extends AppCompatActivity {
    
        private Button btn1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_scroll);
    
            btn1 = (Button) findViewById(R.id.btn1);
    
            btn1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e("TAG" , "onClick") ;
                }
            });
    
            btn1.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    Log.e("TAG" , "onTouch , action: "+event.getAction()) ;
                    return false;
                }
            });
        }
    
    }
    

    0代表down、1代表up,2代表move,一般可能有多个move:

    onTouch返回true,log如下:

    onTouch , action: 0
    onTouch , action: 2
    onTouch , action: 2
    ...
    onTouch , action: 1
    

    onTouch返回false,log如下:

    onTouch , action: 0
    onTouch , action: 2
    onTouch , action: 2
    ...
    onTouch , action: 1
    onClick
    

    现象是:
    如果 onTouch 返回true,表示消费事件,就不会向下传递,就不会执行 onClick,只会执行自己的 down、move(多个move)、up事件;
    如果 onTouch 返回false,执行 down、move(多个move)、up事件,最后执行 onClick;

    下边通过 View的 dispatchTouchEvent 源码 进行分析 View的事件分发;

    3. dispatchTouchEvent源码分析


    前提知识:

    只要触摸任何一个控件,就一定会调用该控件的 dispatchTouchEvent,如果该控件没有,就一路向上查找,直到找到它父类的 dispatchTouchEvent方法然后调用,比如Button如下:


    图片.png
    1>:首先看 View 的 dispatchTouchEvent方法如下:
    public boolean dispatchTouchEvent(MotionEvent event) {  
        // 存放所有的 listener信息
        ListenerInfo li = mListenerInfo;
    
        // mOnTouchListener :只要设置 setOnTouchListener()之后,就不会 null;
        // mViewFlags & ENABLED_MASK:只要 该控件是可点击的,这个就是 true;
        // 主要是mOnTouchListener.onTouch(this, event),这个调用 的是  setOnTouchListener 
        // 的 onTouche() 方法,只要 onTouch() 返回 false,就会执行下边的 onTouchEvent(event)
        if (li != null && mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }
    
    
    static class ListenerInfo {
            protected OnFocusChangeListener mOnFocusChangeListener;
    
            private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
    
            protected OnScrollChangeListener mOnScrollChangeListener;
    
            private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
    
            public OnClickListener mOnClickListener;
            protected OnLongClickListener mOnLongClickListener;
    
            protected OnContextClickListener mOnContextClickListener;
            protected OnCreateContextMenuListener mOnCreateContextMenuListener;
    
            private OnKeyListener mOnKeyListener;
    
            private OnTouchListener mOnTouchListener;
    
            private OnHoverListener mOnHoverListener;
    
            private OnGenericMotionListener mOnGenericMotionListener;
    
            private OnDragListener mOnDragListener;
    
            private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
    
            OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
        }
    

    从 dispatchTouchEvent 方法中可知:首先是if判断,如果 mOnTouchListener != null、mViewFlags & ENABLED_MASK== ENABLED、mOnTouchListener.onTouch(this, event) 这3个 条件 都为真,就 返回 true,否则 执行 onTouchEvent(event)方法并返回;

    第一个条件:mOnTouchListener != null:

    public void setOnTouchListener(OnTouchListener l) {  
        mOnTouchListener = l;  
    } 
    

    可以看到 mOnTouchListener 在 setOnTouchListener() 方法中被赋值,也就是说 只要给控件 设置 setOnTouchListener后, mOnTouchListener 就 会被赋值;

    第二个条件:mViewFlags & ENABLED_MASK== ENABLED:判断当前点击 控件是否是 enable的,Button 默认是 enable的,ImageView 、TextView不是,所以这个 条件是 true;

    第三个条件:mOnTouchListener.onTouch(this, event):

        public interface OnTouchListener {
            boolean onTouch(View v, MotionEvent event);
        }
    

    调用的就是 setOnTouchListener中的 onTouch() 方法,可以看到:
    如果 onTouch 返回 true,那么 这3个条件 都为 true,从而整个方法返回 true;
    如果 onTouch 返回 false,就 执行下边的 onTouchEvent() 方法;

    从这3个条件可知:
    前两个条件肯定都为 true,所以 在 dispatchTouchEvent方法中最先执行 setOnTouchListener的onTouch() 方法, 优先级顺序: onTouch > onClick;
    如果 onTouch 返回 true,dispatchTouchEvent() 整个方法就 返回 true,不会往下执行,onClick 就不会执行;

    onTouchEvent源码如下:

    public boolean onTouchEvent(MotionEvent event) {
    
            // 此处省略一些代码
            ......
    
            // 从这里可知:
            // 如果该 控件是可点击的,比如 Button ,就会进入 switch 判断,在 Action_Up的 case 语句中,
            // 在 经过一系列判断后,会进入到  performClick()方法;
            // 不管当前 的 action 是什么,在 switch 语句外层,都会 返回 true,
    
            // 如果该 控件 不是可点击的,比如 ImageView、TextView,就不会进入 if判断,更不会进入 switch 语句,
            // 直接在  最外层的  if语句 返回 false
    
            if (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                    (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    
                switch (action) {
                    case MotionEvent.ACTION_UP:
    
                                if (!focusTaken) {
                                    // 此处省略一些判断条件
                                    if (!post(mPerformClick)) {
                                        performClick();
                                    }
                                }
                            }
                        break;
    
                    case MotionEvent.ACTION_DOWN:
                    // 此处省略 down 的一些代码
                        break;
    
                    case MotionEvent.ACTION_CANCEL:
                    // 此处省略 cancel 的一些代码
                        break;
    
                    case MotionEvent.ACTION_MOVE:
                    // 此处省略 move 的一些代码
                    break;
              }
                return true;
            }
            return false;
        }
    

    performClick()源码如下:

        public boolean performClick() {
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            return result;
        }
    

    如果 mOnClickListener 不为 null,就会 调用 它的 onClick() 方法,mOnClickListener在 setOnClickListener中赋值的:

        public void setOnClickListener(@Nullable OnClickListener l) {
            if (!isClickable()) {
                setClickable(true);
            }
            getListenerInfo().mOnClickListener = l;
        }
    

    也就是说 只要调用 setOnClickListener时,就会给 mOnClickListener 赋值,只要 Button被点击,就会调用 performClick() 中的 onClick() 方法;

    分析Button:
    在 View的 dispatchTouchEvent()方法中,对于 那3个条件,第三个条件的 setOnTouchListener中的 onTouch()如果返回 false,此时进入 onTouchEvent()方法,这个方法中,因为 Button 是可以点击的,所以就会 进入到 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 语句,会发现 不管当前 action 是什么,都会返回true,这个是系统帮我们返回的;
    分析ImageView:
    给 ImageView 设置 setOnTouchListener(),然后给 它的 onTouch()返回 false,此时进入 onTouchEvent()方法,因为 ImageView 是不可点击的,所以就不会进入 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 语句,直接 在 这个 if() 语句 最外层就返回 false

    4. 结论


    1. touch事件层级传递,就是给控件设置 setOnTouchListener():
    如果给 控件 设置 setOnTouchListener(),就会 触发一系列的 down、move、up事件,如果 down 中返回false,后边的 move、up等一系列事件均不会执行,意思就是 要想触发后边的 某个 move 或者 up事件执行,前边的 down事件就要返回true;

    2. onTouch() 与 onTouchEvent() 区别,如何使用?:
    这两个方法 都是 在 View 的 dispatchTouchEvent() 方法中调用的 , 这里的 onTouch() 其实就是 dispatchTouchEvent() 中的 第三个条件;
    优先级: setOnTouchListener 的onTouch > onTouchEvent();

    如果 onTouch() 返回 true,表示消费事件,那 3个 条件 都为 true,就不会执行下边的 onTouchEvent();

    onTouch 要执行的条件有2个:
    第一:mOnTouchListener不为null,意思就是 给 该 控件 设置了 setOnTouchListener();
    第二:该控件要是 可点击的,就是 enable的;

    如果 点击控件是 非enable的,setOnTouchListener的 onTouch()不会执行,比如ImageView、TextView等, 对于 这类控件,如果想监听它的 onTouch事件,就 需要在 该控件中重写 onTouchEvent方法 来实现;

    比如 ImageView,想监听它的 onTouch事件,有2种方式:

    1. onTouch方法返回 true;
    2. 在布局中给 ImageView 增加 android:clickable="true"的属性;

    相关文章

      网友评论

        本文标题:View事件分发(四) - View事件分发(源码分析)

        本文链接:https://www.haomeiwen.com/subject/bgwlhqtx.html