问题描述:如下图:在点击使用说明的时候,使用说明没有响应点击事件,但是下面的frameLayout中添加的ListView响应了点击事件。

查阅资料了解到了android的事件传递机制,基本如下图所示:

Android 透传点击事件 android 透明点击穿透_android


这个图是借用别人的,但是这个图是重点

关于事件传递机制可以去看看别人的博客,这里我只演示自己的实践过程:
由于最开始我并没有重写我的view,所以并不容易知道,点击屏幕时发生了什么,那么首先重写view,如下:
写一个继承了LinearLayout的类,重写以下三个方法,下面三个方法的作用也可以到别的博客看一下,注释中稍微说明了一下

package com.xcj.XiaoJiaoLuo.Layout;

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

import androidx.annotation.Nullable;

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

//分发任务,返回true表示继续向下分发任务,false不继续向下分发任务
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean bool=super.dispatchTouchEvent(ev);
        Log.d("d","[MyLinearLayout]<"+ ev.getAction()+">任务需要分派?:"+bool);
        return bool;
    }

//拦截任务,返回true表示拦截任务,false不拦截任务
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean bool=super.onInterceptTouchEvent(ev);
        Log.d("d","[MyLinearLayout]<"+ ev.getAction()+">任务需要拦截?:"+bool);

        return bool;
    }

//处理任务,返回true表示该视图成功处理任务,false该视图无法处理任务
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean bool=super.onTouchEvent(event);
        Log.d("d","[MyLinearLayout]<"+ event.getAction()+">自己处理任务?:"+bool);
        return bool;
    }
}

然后就可以在xml中应用重写的布局类,如下:
只需要引用com.xcj.XiaoJiaoLuo.Layout.MyLinearLayout就好了

<com.xcj.XiaoJiaoLuo.Layout.MyLinearLayout
 android:id="@+id/menuList"
 android:layout_width=“wrap_content”
 android:layout_height=“wrap_content”
 app:layout_constraintRight_toRightOf=“parent”
 app:layout_constraintTop_toTopOf=“parent”
 android:background="@drawable/menu_list_shape"
 android:layout_marginTop=“25dp”
 android:layout_marginRight=“25dp”
 android:visibility=“invisible”
 android:orientation=“vertical”>
</com.xcj.XiaoJiaoLuo.Layout.MyLinearLayout>

按照以上例子写了n多个类后,开始演示(当我点击屏幕上的菜单列表发生穿透时)发生了什么错误:
输出如下:

[MyConstraintlayout]<0>任务需要拦截?:false
D/d: [MyFrameLayout]<0>任务需要拦截?:false
    [ReviewLinearLayout1]<0>任务需要拦截?:false
    [ReviewLinearLayout2]<0>任务需要拦截?:false
    [ReviewListView1]<0>任务需要拦截?:false
D/d: [ReviewItemLinearLayout1]<0>任务需要拦截?:false
    [ReviewItemTextView1]<0>自己处理任务?:false
    [ReviewItemTextView1]<0>任务需要分派?:false
    [ReviewItemLinearLayout1]<0>自己处理任务?:false
    [ReviewItemLinearLayout1]<0>任务需要分派?:false
    [ReviewListView1]<0>自己处理任务?:true
    [ReviewListView1]<0>任务需要分派?:true
D/d: [ReviewLinearLayout2]<0>任务需要分派?:true
    [ReviewLinearLayout1]<0>任务需要分派?:true
    [MyFrameLayout]<0>任务需要分派?:true
    [MyConstraintlayout]<0>任务需要分派?:true
    [MainActivity]<0>任务需要分派?:true
D/d: [MyConstraintlayout]<1>任务需要拦截?:false
    [MyFrameLayout]<1>任务需要拦截?:false
    [ReviewLinearLayout1]<1>任务需要拦截?:false
    [ReviewLinearLayout2]<1>任务需要拦截?:false
D/d: [ReviewListView1]<1>自己处理任务?:true
    [ReviewListView1]<1>任务需要分派?:true
    [ReviewLinearLayout2]<1>任务需要分派?:true
    [ReviewLinearLayout1]<1>任务需要分派?:true
    [MyFrameLayout]<1>任务需要分派?:true
    [MyConstraintlayout]<1>任务需要分派?:true
    [MainActivity]<1>任务需要分派?:true
I/Choreographer: Skipped 17 frames!  The application may be doing too much work on its main thread.

由于我没有给出代码,为了方便说明,解释一下视图层级,如下:

MyConstraintlayout{//相对布局
		MyLinearLayout{}//和MyFrameLayout同级,这是菜单列表,被点击时发生穿透的地方
		MyFrameLayout{//FrameLayout布局
			ReviewLinearLayout1{//FrameLayout中add后出现的布局
				ReviewLinearLayout2{
					ReviewListView1{}
				}
			}
		}
	}//大括号内的为子视图

则通过输出可以知道执行的拦截都为false,虽然都没有拦截,但由于ReviewListView1是最低层次的视图(没有子视图),已经不能再向下分发任务了,所以只能在onTouch中处理任务,并且处理成功了,因此出现了我们看到的那个现象,点击穿透了。我们明明的点的上层布局MyLinearLayout,但是MyLinearLayout中的拦截,分发和处理任务的方法压根就没有一个被执行,而是同级视图MyFrameLayout中的子视图ReviewListView1成功处理了任务。那么我们应该怎么才能不让ReviewListView1处理任务。
解决办法:
很明显,我们只需要使ReviewLinearLayout2或它的上级将任务拦截下来就好了,如下:返回true就会拦截任务,拦截任务后自身处理不了任务,也不会像子视图传递任务,而是将任务返回给上级视图或同层级视图,直到碰到同层级视图MyLinearLayout,则由他开始处理任务,就成功的解决了问题

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean bool=super.onInterceptTouchEvent(ev);
    Log.d("d","[ReviewLinearLayout2]<"+ ev.getAction()+">任务需要拦截?:"+bool);
    //如果菜单列表是打开的,则拦截任务不允许点击单词列表
    if(MainActivity.isVisibility){
        return true;
    }
    return bool;
}

总结:如果点击上层视图发生穿透,下层视图响应,只需要在任务传递到下层视图之前拦截任务就好了,至于那些使用setCilckable或设置xml中cickable解决问题的,并没有接触到问题的本质,事实上setCilckable也只是使view中重写的三个方法返回true或者false,使用他们并没有直接重写view来的直接,灵活。