Android View setEnabled false 没有效果的解决方法

问题场景

今天遇到了一个很诡异的问题,Android 中 setEnable(false) 没有作用,网上找的很多方法还是没用。会出现这种情况的有下面几个场景:

  1. ListView 中的子视图 setEnable(false) 后点击仍然会响应 onItemClick
  2. FrameLayout 中上层试图 disable 后,上层试图没有响应事件,但是 touch 事件传递到下层视图了
  3. ViewGroup 调用 setEnable(false),不会自动把子视图设为 disabled

针对第一个场景,同事给出了一个解决方法:设置一个空的 onClickListener

1
2
3
4
5
6
7
view.setEnabled ( false );
view.setOnClickListener( new OnClickListener() {
@Override
public void onClick(View v) {
// nothing, just to fix the bug that the view can be clicked.
}
} );

其实上面的做法就是消费点击事件但是什么都不做,并不是一般情况下的不消费点击事件。在 Android 的 View 源码中我找到了产生这个效果的原因,具体看下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
// ENABLED_MASK为DISABLED时,且isClickable()或isLongClickable()为true时,onTouchEvent 直接return true,消费了事件却不做其他事情
// 而setOnClickListener时,会setClickable(true).
...
}
/**
* 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(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

场景解析

现在再来分析开始提到的三个场景:

  1. ListView 的子试图为 disabled 后不消费 touch 事件,接下来事件回传给 ListView,传到 ListView 后调用 onItemClick 方法,所以子视图应该消费 touch 事件,可以用上面的方法解决。

  2. FrameLayout 中的上层视图 disabled 后,touch 事件继续传递到下面的视图跟场景 1 是一样的,都可以通过前面提过的解决方法解决,不过 setOnClickListener 可以用 setClickable 替代。

1
2
view.setEnabled(false);
view.setClickable(true);
  1. ViewGroup 设为 disabled 之后,子视图仍然可以正常点击,这个问题网上很多人都说用遍历子视图逐个 setEnable(false) 的方法,不过都是用递归算法实现的,下面我给出循环的实现方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void setEnabled(View view, boolean enabled) {
if(null == view) {
return;
}
if(view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
LinkedList<ViewGroup> queue = new LinkedList<ViewGroup>();
queue.add(viewGroup);
// 遍历viewGroup
while(!queue.isEmpty()) {
ViewGroup current = queue.removeFirst();
current.setEnabled(enabled);
for(int i = 0; i < current.getChildCount(); i ++) {
if(current.getChildAt(i) instanceof ViewGroup) {
queue.addLast((ViewGroup) current.getChildAt(i));
}else {
current.getChildAt(i).setEnabled(enabled);
}
}
}
}else {
view.setEnabled(enabled);
}
}