前不久在做一个社交应用时,遇到了这样一个问题:点击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);
}
}
}
}
当点击Android的ViewGroup时,其子控件也会错误地显示为被点击状态,尽管它们的onClick回调并未执行。这个问题源于stateListDrawable的使用和ViewGroup的dispatchSetPressed方法。在早期版本的Android中,该方法会导致子控件也被设置为按下状态。通过重写ViewGroup的dispatchSetPressed方法,使其与新版本的实现一致,并将子控件的clickable属性设为true,可以解决此问题。
646

被折叠的 条评论
为什么被折叠?



