点击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

时间: 2024-10-23 08:46:24

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

连接SQLServer时,因启用连接池导致孤立事务的原因分析和解决办法

本文出处:http://www.cnblogs.com/wy123/p/6110349.html 之前遇到过这么一种情况: 连接数据库的部分Session会出现不定时的阻塞,这种阻塞时长时短,有时候持续较长时间,有时间持续时间较短,没有什么规律.  之后分析相关存储过程和代码写法,发现是存储过程中开启了事务,而应用程序在调用存储过程发生异常之后没有进行特别的处理(提交或者回滚),  那么在执行方法发生异常之后,连接关闭了,但是数据库中遗留有活动事务(dbcc opentran对应的Session

Android学习分享:执行某ViewGroup的动画时,子控件太多导致动画执行卡顿的问题

最近在项目中遇到一个问题,我有一个LinearLayout,里面装载了许多ImageView控件,ImageView控件显示着自己的图片,这个LinearLayout支持双指缩放,缩放采用ScaleAnimation来实现,但是但是在缩放过程中,屏幕十分卡顿,缩放效果根本没有跟上手指的缩放动作.后来在Google上查了一番,查到一个API,叫setAnimationDrawCacheEnabled(boolean enabled): /** * Enables or disables the c

报表reportviewer控件使用时,单行文本过长的解决办法

最近在做winform的报表,客户需要把纸质模板可以在系统中打印.一开始设计,认为vs自带控件reportviewer可以解决,但在测试过程中发现:若单行文本过长,报表自动分页,弊端是分页后,第一页文本框底部边框和第二页文本框顶端边框无法显示,由于客户是政府机关,要求严格,只好解决这个问题.在解决过程中也是想各种办法,第一种,判断是否分页,分页后,该文本框是否存在本页,若存在,则在相应分页底部和顶端划线,可是搜索属性框,并没有这些属性值,放弃.第二种,主体加边框,与矩阵重合,矩阵边框不要,使用主

DuiLib中的GifAnim控件无法加载或者播放Gif动画的解决办法

一.无法加载gif图片的问题,因为GifAnim控件加载图片使用的是Gdi+,然而在控件中并没有初始化Gdi+,所以只需要在自己的主窗口类中添加如下代码: 1 // 全局 2 Gdiplus::GdiplusStartupInput gdiplusStartupInput; 3 ULONG_PTR gdiplusToken; 4 // 初始化函数中 5 //初始化GDI+. 6 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL

MJ刷新控件MJRefreshFooterView上拉之后收不回来的解决办法

修改MJRefreshFooterView.m文件中的这个方法 #pragma mark - 状态相关 #pragma mark 设置状态 - (void)setState:(MJRefreshState)state { if (_state == state) return; MJRefreshState oldState = _state; [super setState:state]; switch (state){ case MJRefreshStatePulling:{ _status

join时显示no join predicate原因分析以及解决办法

本位出处:http://www.cnblogs.com/wy123/p/6238844.html 最近遇到一个存储过程在某些特殊的情况下,效率极其低效, 至于底下到什么程度我现在都没有一个确切的数据,因为预期很快就可以查询出来结果的SQL,实则半个小时都出不来,后面会有截图 观察执行计划的时候发现中间有一步中出现一个类似如下非常规的连接提示警告,如下图 no join predicate 意思就是没有连接谓词,表之间join的时候没有指定连接谓词可以导致no join predicate, 但是

ListView中的Item点击事件和子控件的冲突或者item点击没有反应的解决办法

fragment中添加了button和checkbox这些控件,此时这些子控件会将焦点获取到,所以常常当点击item时变化的是子控件,item本身的点击没有响应. 这时候就可以使用descendantFocusability来解决啦,API描述如下: android:descendantFocusability 该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系. 属性的值有三种: beforeDescendants:viewgroup会优先其子类控件而获取到焦点

iOS开发——实战技术OC篇&amp;点击状态栏ScrollView(包括子控件)自动滚到顶部

点击状态栏ScrollView(包括子控件)自动滚到顶部 其实这种方式我们平时见的还是比较多的,而且适合用户的需求,所以就搬来琢磨了一下,感觉效果还不错 这里就直接将解决思路一一写出来不将代码分段展示了,在代码中我加了详细的注释objective-c的套路和swift基本一样,在最后会将Swift和objective-c的代码一起放上,如果需要直接解决问题的童鞋可以直接将代码拷贝到工程里即可 首先创建一个topWindow继承至NSObject,这里我们考虑将这个功能完全封装起来,所以所有的方法

Android--根据子控件的大小自动换行的ViewGroup

1.自定义ViewGroup 1 /** 2 * Created by Administrator on 2016/2/26. 3 * 4 * --------自动换行的ViewGroup----------- 5 */ 6 public class LineWrapLayout extends ViewGroup { 7 private static final boolean DEBUG = true; 8 private static final String TAG = "AutoLin