点击ViewGroup时其子控件也变成pressed状态的原因分析及解决办法

  这个问题,当初在分析touch事件处理的时候按理应该分析到的,可是由于我当时觉得这块代码和touch的主题不是那么紧密,

就这么忽略掉了,直到后来在这上面遇到了问题。其实这个现象做Android开发的应该或多或少的都遇到过,我在我们自己的app中

也发现了这一现象,当初是百思不得其解,因为按照我自己的研究、分析,只有在一个view接受按下的touch事件时,才会调到view

自己的setPressed方法,从而改变background状态啊。这里的case明显没有按下这个子view啊,按下的是ViewGroup啊,所以一直

没想通,当时也就没多想,就这么不明不白的过去了(现在回过头来想,其实只要在android src中搜索下setPressed方法都在哪调用了,

这个问题就迎刃而解了)。直到有一天,我们的公司的Review中又再次遇到了相关的问题,巧合的是我们的这个fix把原先的bug解了,但

不幸的是引入了本文的这个问题。问题又一次出现了,同时我们的另一名同事给我看了段ViewGroup里的代码,我当时的第一感觉是又兴奋

又惊讶。兴奋是困扰我许久的问题终于有答案了,惊讶是不太能理解为啥Android会写这样一段在我看来有点“多管闲事”的代码。好了,故事

背景交代的差不多了,下面直接进入主题。

  对touch事件有了解的同学大体都知道,View.setPressed(true)调用发生在View.onTouchEvent方法中,视情况不同要么是DOWN

事件、或者DOWN事件推迟TAP_TIMEOUT毫秒之后,要么是UP事件处理的时候。我们来看下相关源码:

    /**
* Sets the pressed state for this view.
*
* @see #isClickable()
* @see #setClickable(boolean)
*
* @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
* the View's internal state from a previously set "pressed" state.
*/
public void setPressed(boolean pressed) {
// 检查pressed状态是否改变
final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED); if (pressed) { // 设置标志位
mPrivateFlags |= PFLAG_PRESSED;
} else {
mPrivateFlags &= ~PFLAG_PRESSED;
} if (needsRefresh) { // 状态改变了,那么需要刷新下drawable state,在这里background会变成合适的样子
refreshDrawableState();
}
dispatchSetPressed(pressed); // 将pressed状态传递到子view
} /**
* Dispatch setPressed to all of this View's children.
*
* @see #setPressed(boolean)
*
* @param pressed The new pressed state
*/
protected void dispatchSetPressed(boolean pressed) { // view的默认是do nothing,因为它也没children啊
} // ViewGoup对dispatchSetPressed的重载
@Override
protected void dispatchSetPressed(boolean pressed) { // 这是ViewGroup对其的实现
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = 0; i < count; i++) {
final View child = children[i];
// Children that are clickable on their own should not
// show a pressed state when their parent view does.
// Clearing a pressed state always propagates. 注意理解这段注释
if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
// 其实这个if主要是做了2件事,1如果pressed是false则总是调用child的setPressed(false)方法,也就是说这种
// 情况下always传递;2如果pressed是true,则只有child不能响应touch事件即clickable、longClickable都是false
// 的时候才调用child.setPressed(true)方法。说白了,child自己能处理pressed事件则他们自己会处理,否则parent会
// 强迫child处理。同时注意下,这个调用还是递归的,会一直传递给子孙后代。。。
child.setPressed(pressed);
}
}
}

  另外需要提1点就是直到Android4.1.x开始ViewGroup.dispatchSetPressed方法才在遍历children时加了这个if判断,也就是说之前

的版本会直接调用child.setPressed(pressed)方法。从上面的分析可以看出,由于这里parent“强迫”child处理pressed事件,所以为了避

免出现本文一开始的现象(或者叫bug),你有2种方式:

1. 让某个view自己能处理touch事件,也即设置clickable、longClickable为true;

2. 如果某个view自己不处理touch事件,那么你就不应该给它设置个stateful的drawable,或者至少不应该给它pressed状态设置一个单独

的drawable,这样即使发生了setPressed(true)调用,也没关系。

我们代码里,选用了第2种解决方式,直接在代码中去掉了其background。这篇小文章算是对前面touch事件处理的补充也是开发过程中的

经验、教训,希望对大家有所帮助,enjoy。。。

  最后,附上一篇csdn的同主题文章:http://blog.csdn.net/cpyyes/article/details/11144497

上一篇:js数据类型转换 ----流程控制


下一篇:Linux 学习 (二) 文件处理命令