Android -View 点击事件的分发机制

热门标签

特别声明:文章多为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

这里面的代码以及文字来自 任玉刚的 <<android开发艺术探索>> 此处仅作为个人笔记使用

/**
     * 用来进行事件的分发,如果事件能够传递给当前view,则此方法一定会被调用,返回结果受onTouchEvent
     * 和下级的dispatchTouchEvent影响
     * @param event
     * @return 表示是否消耗了该点击事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean consume=false;
        if (onInterceptTouchEvent(event)){
            consume=onTouchEvent(event);
        }else {
            consume= child.dispatchTouchEvent(event);
        }
        return consume;
    }

    /**
     * 在dispatchTouchEvent内部调用,用来判断是否拦截某个某个事件,如果当前View 拦截了某个事件,
     * 那么在同一个事件序列中,此方法不会被再次调用
     * @param ev
     * @return 表示是否拦截当前事件.
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        throw new RuntimeException("Stub!");
    }

    /**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

viewgroup的事件处理如图所示

Android -View 点击事件的分发机制

view的点击处理逻辑

Android -View 点击事件的分发机制

点击事件的传递过程

activity->window->view

顶级的view接收到点击事件以后,就会按照分发机制一层层的分发事件.如果其中的某一个view的ontouchevent返回了false,那么它父容器的ontouchevent将会被调用

结论:

  1. 同一事件序列包括down,move,up
  2. 正常情况,一个事件序列全部被一个view消耗.特殊情况是view强行使用onTouchEvent传递
  3. view一旦决定拦截,那么这个事件序列全部归它,并且onInterceptTouchEvent不会再被调用
  4. view不消耗down事件,那么事件序列其他事件也与它无缘.会再次给父控件onTouchEvent
  5. 如果view只消耗down事件,那么这个点击事件会消失,会直接传递给activiy
  6. viewGroup默认不拦截任何事件
  7. view没有onInterceptTouchEvent,只要收到事件就调用onTouchEvent
  8. view的onTouchEvent默认返回true.如果clickable和longClick同时为false,那么返回false.
  9. view的enable不影响onTouchEvent
  10. onclick发生的条件 a.view可点击 b.收到了down和up
  11. 事件传递由外向内.子view可以通过requestDisallowInterceptTouchEvent干涉父元素除了down事件以外的其他事件

实践

好了经过上面的理论讨论,我们来实践一下.我们新建两个自定义控件,一个是继承LinearLayout一个继承Button.具体代码如下

public class TestLayout extends LinearLayout {
    private static final String TAG = "TestLayout";
    public TestLayout(Context context) {
        super(context);
    }

    public TestLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TestLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 用来进行事件的分发,如果事件能够传递给当前view,则此方法一定会被调用,返回结果受onTouchEvent
     * 和下级的dispatchTouchEvent影响
     * @param event
     * @return 表示是否消耗了该点击事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "dispatchTouchEvent: "+event.getAction());
        return  super.dispatchTouchEvent(event);
    }

    /**
     * 在dispatchTouchEvent内部调用,用来判断是否拦截某个某个事件,如果当前View 拦截了某个事件,
     * 那么在同一个事件序列中,此方法不会被再次调用
     * @param ev
     * @return 表示是否拦截当前事件.
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent: "+ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: "+event.getAction());
        return super.onTouchEvent(event);
    }
}
public class TestBtn extends Button{
    private static final String TAG = "TestBtn";
    public TestBtn(Context context) {
        super(context);
    }

    public TestBtn(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TestBtn(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 用来进行事件的分发,如果事件能够传递给当前view,则此方法一定会被调用,返回结果受onTouchEvent
     * 和下级的dispatchTouchEvent影响
     * @param event
     * @return 表示是否消耗了该点击事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "dispatchTouchEvent: "+event.getAction());
        return super.dispatchTouchEvent(event);
    }


    /**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: "+event.getAction());
        return super.onTouchEvent(event);
    }


}

剩下activity和布局的代码太简单这里我们就不贴了,我们直接运行一下.然后点击一下按钮.

可以得到以下运行结果

1. down事件:

TestLayout:dispatchTouchEvent -> TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

2. move事件:

TestLayout:dispatchTouchEvent ->TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

3. up事件:

TestLayout:dispatchTouchEvent ->TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

4. button的click事件

以上我们可以得到以下结论:

1.一开始我们的流程是对的

2.验证了上面结论的1,2,6,7

3.click事件是最后触发的

我们现在再修改代码,将testBtn的onTouch返回值更改为false

Android -View 点击事件的分发机制
    /**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: "+event.getAction());
        boolean isTouch=super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent: "+isTouch);
        return false;
    }
Android -View 点击事件的分发机制

然后再运行一下代码,看一下获得的结果

1. down事件:

TestLayout:dispatchTouchEvent -> TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent->TestLayout:onTouchEvent

然后就没了,对,没了,没有move事件,也没有up事件,也没有点击事件

我们可以得到以下结论:

1.首先down事件正常传递,当传递到testBtn的时候,testBtn的onTouchEvent返回false,代表了没有对点击事件进行处理.那么按照上面我们总结的结论的第4条,很自然的就调用了父控件也就是TestLayout的onTouchEvent方法.由于viewgroup的onTouchEvent的返回值为

false.所以最终这个事件序列,他们两个都木有处理,所以这个事件序列的其他事件自然与他们无关了

2.并且从这里我们也可以看出,如果testBtn只收到down没有收到up,是不会调用onclick方法的

 

现在我们进一步修改代码,我们将testLayout的onTouchEvent的返回值由false更改为true.看看会发生什么

/**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: "+event.getAction());
        boolean isTouch=super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent: "+isTouch);
        return true;
    }

好的,我们运行一下程序,看一下会发生什么

1. down事件:

TestLayout:dispatchTouchEvent -> TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent->TestLayout:onTouchEvent

2. move事件:

TestLayout:dispatchTouchEvent ->TestLayout:onTouchEvent

3. up事件:

TestLayout:dispatchTouchEvent ->TestLayout:onTouchEvent

总结

其实这次获得的结论和上面的测试获得的结论是一样的.只是更新验证了我们的结论.当然我们也可以看到,如果下面的view没有消耗事件流的down事件,那么后面的事件都和它无关.并且我们也可以看到上面结论3也被证实了.

好了,我们看一下之前总结的结论,除了5,8,9.其他的,都得到了证实.那么我们接下来就来验证结论5,8,9.

先是结论5

我们将testLayout的onTouchEvent恢复成原来的样子,返回值更改为false

将testBtn的onTouchEvent改成下面的样子

/**
     * 在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,
     * 则在同一个事件序列中,当前view无法再次接收到事件.
     * @param event
     * @return 表示是否消耗当前事件,
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: "+event.getAction());

        boolean isTouch=super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent: "+isTouch);
        if (event.getAction()==MotionEvent.ACTION_DOWN){
            return true;
        }else {
            return false;
        }

    }

运行后我们会获取结果:

1. down事件:

TestLayout:dispatchTouchEvent -> TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

2. move事件:

TestLayout:dispatchTouchEvent ->TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

3. up事件:

TestLayout:dispatchTouchEvent ->TestLayout:onInterceptTouchEvent->TestBtn:dispatchTouchEvent->TestBtn:onTouchEvent

4. button的click事件

乍一看这个结果,咦,不是和第一个button和layout什么都不改不是一样的嘛.其实就是一样的.但是你发现没有,其实move事件和up事件,我们没有任何控件去消耗它.它们就这样消失了,即没有被testButton消耗,也没有被testLayout消耗

 

下面我们再把现场恢复到默认的样子.

然后在activity中添加一个

testBtn.setClickable(false);

但是请注意,这个设置一定要在setOnClickListener以后调用,源码嘛,看源代码

/**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

好的,现在我们运行以后,很明显的就能看到运行结果,testBtn的onTouchEvent的返回值编程了false,很轻易的验证了,我们的结论8.同时也知道了为什么TextView的onclickable为false,还能添加监听

标签:

未经允许不得转载:作者:SheaYang, 转载或复制请以 超链接形式 并注明出处 技术Dog|博客
原文地址:《Android -View 点击事件的分发机制》 发布于2019-10-11

分享到:
赞(0)

评论 抢沙发

1 + 5 =


Android -View 点击事件的分发机制

长按图片转发给朋友

Vieu4.0主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录