IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Android-点击事件分发机制验证

    summer发表于 2016-05-27 18:12:16
    love 0

    简介


    点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生之后,系统需要这个事件传递给一个具体的View,而这个传递过程就是分发过程。
    点击事件的分发过程由三个重要方法共同完成:
    • dispatchTouchEvent
      事件分发
    • onInterceptTouchEvent
      事件拦截
    • onTouchEvent
      事件响
      应

    方法介绍


    public boolean dispatchTouchEvent(MotionEvent ev)
    用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受到当前View的onTouchEvent和下级View的方法的影响。
    • 如果return true,表示点击事件被消耗掉。
    • 如果return false,表示点击事件没有被消耗
    public boolean onInterceptTouchEvent(MotionEvent
    ev) 
    用来判断是否拦截某个事件。
    • 如果return true,表示点击事件被拦截,并将拦截到的事件交由当前 View 的 onTouchEvent
      进行处理
      。
    • 如果return false,表示点击事件未被拦截,View上的事件会被传递到子View上,由子View的dispatchTouchEvent来处理。
    public boolean onTouchEvent(MotionEvent ev)
    用来处理点击事件,返回结果表示是否消耗当前事件。
    • 如果return true,表示点击事件被接收并消费。
    • 如果return false,表示点击事件未被消费。

    三个方法之间的关系

    public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean consume = false;
            if (onInterceptTouchEvent(ev)) {
                consume = onTouchEvent(ev);
            } else {
               if(hasChild()){
                    consume = child.dispatchTouchEvent(ev);
                } else{
                    consume = onTouchEvent(ev);
                }

            }

            return consume;
        }


    代码实践


    界面UI布局


    ParentLayout

    后面例子,都是在此代码的基础上修改的
    public class ParentLayout extends LinearLayout {

        public ParentLayout(Context context) {super(context);}

        public ParentLayout(Context context, AttributeSet attrs) {super(context, attrs);}

        public ParentLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.dispatchTouchEvent(ev);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.onInterceptTouchEvent(ev);
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.onTouchEvent(ev);
        }
    }

    ChildView

    后面例子,都是在此代码的基础上修改的
    public class ChildView extends TextView {

        public ChildView(Context context) {super(context);}

        public ChildView(Context context, AttributeSet attrs) {super(context, attrs);}

        public ChildView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.dispatchTouchEvent(ev);
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.onTouchEvent(ev);
        }
    }

    打印结果

    点击ChildView(1处),打印Log 

    点击ParentView(2处),打印Log


    处理过程解释

    代码实践这种情况下,这三个方法,默认都发送false。对于三个方法return的介绍参考前面方法介绍。这里,我们以点击ChildView为例子,再次强调点击实践的分发流程: 

    dispatchTouchEvent方法再探

    事件分发-false

    在ChildView代码基础上,将dispatchTouchEvent方法内容改为如下形式
    public class ChildView extends TextView {
        ...
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ABLog.e("Default Return " + String.valueOf("false"));
            return false;
        }
    点击ChildView(1处),打印Log 

    事件分发-true

    在ChildView代码基础上,将dispatchTouchEvent方法内容改为如下形式
    public class ChildView extends TextView {

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ABLog.e("Default Return " + String.valueOf("true"));
            return true;

        }
    点击ChildView(1处),打印Log
    触发两次Log打印?因为当ChildView的dispatchTouchEvent返回true时,这就表示一系列点击事件都由ChildView处理,所以,两次Log分别为手势的Down和Up事件。而如果返回false时,这就表示一系列点击事件不由ChildView处理,所以,只打印一次。
     

    onTouchEvent方法再探

    事件响应-false

    在ChildView代码基础上,将onTouchEvent方法内容改为如下形式
    public class ChildView extends TextView {

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            ABLog.e("Default Return " + String.valueOf("false"));
            return false;
        }

    点击ChildView(1处),打印Log
    和默认效果一样,因为默认也是返回false。 


    事件响应-true

    在ChildView代码基础上,将onTouchEvent方法内容改为如下形式
    public class ChildView extends TextView {

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            ABLog.e("Default Return " + String.valueOf("true"));
            return true;
        }

    点击ChildView(1处),打印Log
    触发两次Log打印?原因同上,这一系列事件都有ChildView出发。 

    onInterceptTouchEvent方法再探

    事件拦截-false

    在ParentLayout代码基础上,将onInterceptTouchEvent方法内容改为如下形式
    public class ParentLayout extends LinearLayout {

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("false"));
            return false;

        }
    点击ChildView(1处),打印Log 


    点击ParentView(2处),打印Log

    总结一下上面Log情况?Log结果都和一开始全部默认情况相同。onInterceptTouchEvent为false的情况,就是调用super.onInterceptTouchEvent的情况。onInterceptTouchEvent返回false,表示点击事件不拦截,向子View传递。

    事件拦截-true 事件响应-false

    在ParentLayout代码基础上,将ParentLayout改为如下形式
    public class ParentLayout extends LinearLayout {

       @Override
       
    public boolean dispatchTouchEvent(MotionEvent ev){
           
    ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.dispatchTouchEvent(ev);
        }

        @Override
       
    public boolean onInterceptTouchEvent(MotionEvent ev){
           
    ABLog.e("Default Return " + String.valueOf("true"));
           
    return true;
        }

        @Override
       
    public boolean onTouchEvent(MotionEvent ev){

           ABLog.e("Default Return " + String.valueOf("false"));

           return false;
        }

    }
    点击ChildView(1处)或者ParentLayout(2处),打印Log
    为什么点击ChildView和ParentLayout显示相同Log?因为MotionEvent一系列事件,已经被onInterceptTouchEvent拦截了,所以MotionEvent一系列事件将不会被传递到子View(ChildView)中。
    为什么拦截了点击事件,但是Log只出现了一次?因为使用onInterceptTouchEvent拦截了点击事件,只是拦截了点击事件的传递,并没有处理MotionEvent的一系列事件。更进一步说,只有dispatchTouchEvent方法返回true,才表示MotionEvent的一系列事件被处理。onTouchEvent方法返回true,只是间接的对dispatchTouchEvent产生了影响。 

    事件拦截-true 事件响应-true

    在ParentLayout代码基础上,将ParentLayout改为如下形式
    public class ParentLayout extends LinearLayout {
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("default: false"));

            return super.dispatchTouchEvent(ev);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("true"));
            return true;
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev){
            ABLog.e("Default Return " + String.valueOf("true"));
            return true;
        }

    }
    点击ChildView(1处)或者ParentLayout(2处),打印Log
    为什么Log和之前看到的不一样?对于这次的Log,其实还是包含了点击事件Down和Up。在这我们用分割线分开,第一部分,出现了dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent三个函数;第二部分,因为onInterceptTouchEvent(拦截方法),只被调用一次哦,所以只出现了dispatchTouchEvent,
    onTouchEvent两个函数。 

    View中Listener的优先级


    主要探究OnTouchListener和OnClickListener还有onTouchEvent三个方法之间的关系: 

    界面

    ChildView

    public class ChildView extends TextView {

        public ChildView(Context context) {
            super(context);
        }

        public ChildView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public ChildView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            ABLog.e("Default Return " + String.valueOf("false"));
            return super.onTouchEvent(ev);
        }
    }

    MainActivity

    public class MainActivity extends AppCompatActivity {

        @Override
       
    protected void onCreate(Bundle savedInstanceState) {
           
    super.onCreate(savedInstanceState);
            setContentView(
    R.layout.activity_main);

            ChildView view = (ChildView)findViewById(R.id.myChildView);
            view
    .setOnClickListener(new View.OnClickListener() {
               
    @Override
               
    public void onClick(View v) {
                   
    ABLog.e("OnClickListener");
                }
            });

            view.setOnTouchListener(new View.OnTouchListener() {
               
    @Override
               
    public boolean onTouch(View v, MotionEvent event) {
                   
    ABLog.e("setOnTouchListener");
                   
    return false;
                }
            });
        }

    }
    点击ChildView(1处),打印Log
    前一部分,为Action_Down触发;后一部分,为Action_Up触发。 


    onTouch方法再探

    将onTouch方法返回值改为true,表示处理点击时间的一系列事件,将setOnTouchListener方法改为:
    view.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    ABLog.e("setOnTouchListener");
                    return
    true
    ;

                }
            });

    点击ChildView(1处),打印Log
    点击时间在OnTouch方法中被处理,所以不会传到onTouchEvent方法中去了。因此更加不会传到OnClickListener监听方法中去了。
    一个为Action_Down触发,一个为Action_Up触发。 






沪ICP备19023445号-2号
友情链接