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

  这个问题,当初在分析touch事件处理的时候按理应该分析到的,但是因为我当时以为这块代码和touch的主题不是那么紧密,android

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

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

本身的setPressed方法,从而改变background状态啊。这里的case明显没有按下这个子view啊,按下的是ViewGroup啊,因此一直this

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

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

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

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

背景交代的差很少了,下面直接进入主题。递归

  对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

相关文章
相关标签/搜索