android中点击viewgroup时,子控件也会变成被点击的状态的原因及解决方法

当点击Android的ViewGroup时,其子控件也会错误地显示为被点击状态,尽管它们的onClick回调并未执行。这个问题源于stateListDrawable的使用和ViewGroup的dispatchSetPressed方法。在早期版本的Android中,该方法会导致子控件也被设置为按下状态。通过重写ViewGroup的dispatchSetPressed方法,使其与新版本的实现一致,并将子控件的clickable属性设为true,可以解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前不久在做一个社交应用时,遇到了这样一个问题:点击listview的item时,里面若含有子控件(button或者image等等)且设置了selector,则也会变成被点击的状态。而此时,其实是没有点中这些控件的。这些控件的onClick回调并不会被执行,只是外观发生了变化。

在解决问题之前,我们先来说说的stateListDrawable。

stateListDrawable是用xml定义的用用多张图片去表示某一个图像的drawable对象,它在不同的状态下会显示不同的图片。这取决于你在xml中的设置。

下面是官网上列出来的状态类型:

?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=["true" | "false"]
    android:dither=["true" | "false"]
    android:variablePadding=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:state_pressed=["true" | "false"]
        android:state_focused=["true" | "false"]
        android:state_hovered=["true" | "false"]
        android:state_selected=["true" | "false"]
        android:state_checkable=["true" | "false"]
        android:state_checked=["true" | "false"]
        android:state_enabled=["true" | "false"]
        android:state_activated=["true" | "false"]
        android:state_window_focused=["true" | "false"] />
</selector>
看到这里,我们发现了一个表象上的原因,那就是当我们点击viewgroup时,里面的控件的状态也被修改为pressed了。也就是说,viewgrouop把pressed状态传递到了它的子控件中。为了解决这个问题,我们得去看看pressed状态是如何传递的。

当一个view被按下时,setPressed(boolean pressed)会被调用,用来设置该view被点击的的状态。当pressed为true时,view的状态为pressed。也即会显示被点击时的图片。下面是sdk2.1(我使用的是2.1)中setPressed函数的代码:

public void setPressed(boolean pressed) {
	if (pressed) {
		mPrivateFlags |= PRESSED;
	} else {
		mPrivateFlags &= ~PRESSED;
	}
	refreshDrawableState();
	dispatchSetPressed(pressed);
}
最后的dispatchSetPressed(pressed)函数负责的就是把pressed状态传递给子控件。

所以,我们再来看看ViewGroup对dispatchSetPressed的重写:

@Override
protected void dispatchSetPressed(boolean pressed) {
	final View[] children = mChildren;
	final int count = mChildrenCount;
	for (int i = 0; i < count; i++) {
		children[i].setPressed(pressed);
	}
}
惊讶地发现ViewGroup竟然没有任何判断和约束地遍历了一遍子view,将自己的pressed状态传到每一个子View去了。我觉得这算是SDK2.1设计上的一个缺漏吧。

再来看看SDK4.1的代码:

@Override
protected void dispatchSetPressed(boolean pressed) {
	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())) {
			child.setPressed(pressed);
		}
	}
}
果然,在后面的版本中对这部分作了改进,加了一层判断,当子View自身可以被点击时,pressed状态不会被传递过去。


看到这里,相信大家都知道如何去解决了。那就是创建一个新的类,继承ViewGroup(或者LinearGroup,RelativeGroup等ViewGroup的子类),重写dispatchSetPressed函数,改写为4.1版本的代码。用它来替代原来的ViewGroup,然后将其子控件的clickable设成true即可。

下面是我重写的代码:

public class UnpressedLayout extends RelativeLayout{
	private boolean pressEnable = true;
	public unpressedLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	protected void dispatchSetPressed(boolean pressed) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
                child.setPressed(pressed);
            }
        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值