两层view叠加带来的响应问题

浅析Android View的触摸分发机制

Posted by 夏敏的博客 on July 12, 2016

Andorid

前几天在公司遇到一个双层view叠加,而此时系统存在click声音导致点击上层view空白处有声音的问题。

双层view叠加不同于单个view的触摸事件分发机制,单个view的触摸事件


我们先看对于一个viewGroup来说,触摸事件的分发

ViewGroup分发.png 一般来说,开发Android应用程序的UI界面都不会直接实用View和ViewGroup,而是使用这两大基类的派生类。

Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);

1.事件分发:(看过我上篇Fragment中监听触摸事件的兄弟就该知道该方法的妙用)

public boolean dispatchTouchEvent(MotionEvent ev)

该方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev)方法对事件进行分发

该方法的处理

  • return true,事件会分发给当前 View 并由 dispatchTouchEvent方法进行消费,同时事件会停止向下传递;
  • return false,事件分发分为三种情况:
    如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费
    如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的onTouchEvent 进行消费
    如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法

2.事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。

该方法的处理逻辑:

  • return true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
  • return false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的dispatchTouchEvent 来开始这个事件的分发;
  • 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认会被拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理。

3.事件响应:public boolean onTouchEvent(MotionEvent ev)

在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。

onTouchEvent 的事件响应逻辑如下:

  • return false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
  • return true 则会接收并消费该事件。
  • return super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。

那么,我们的触摸事件分发图也就诞生了。

触摸事件分发.png


解决问题

理解了触摸事件的分发后,我们面临的问题是,叠加后的底面那层响应了点击事件,且发出了点击声音。而我们底面的view并不是上层view的子view,其是类似于frame叠加的效果。

那么我们可以分析出,是我们点击上层view空白处,touch事件没有被消费的问题,在上层空白处,我们的触摸事件被分发到了下一层,通过==uiautomatorviewer==进行UI分析发现存在view叠层的问题,此时问题解决方法就出来了,我们只需在上层view,触摸事件没被处理的情况下自己主动return ture; 告诉系统我们响应了触摸事件了即可,此时触摸事件便不会被系统分发给下一层的view

后来发现setting中很多地方存在该问题,好在那些view都是继承的LinerLayout于是我写了个TouchEventConsumerLayout父类,让他们都继承TouchEventConsumerLayout,问题得到了解决。且子view

click事件都能正常发生。
毕竟,clicklistener是优先处理的。且子view的触摸事件也是优先处理的。

好了,让我们看一下该类。

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class TouchEventConsumerLayout extends LinearLayout{

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }

}

谢谢大家阅读,如有帮助,来个喜欢或者关注吧!


本文作者:Anderson/Jerey_Jobs

博客地址 : 夏敏的博客/Anderson大码渣/Jerey_Jobs 简书地址 : Anderson大码渣 CSDN地址 : Jerey_Jobs的专栏 github地址 : Jerey_Jobs

作者:Anderson大码渣,欢迎关注我的简书: Anderson大码渣