ok,到了第二篇博客,我们继续解决问题。
- 子view中的button等劫持了listview中item的clickListener。
- OnClickListener接口的onClick方法中的View view参数究竟是什么
我们常在listview的item中设置自己喜欢的样式,这其中可能就会出现button和checkBox这样的控件,你可能会遇见item中的button拦截了listview的onItemClickListener,导致其点击事件失效。
这个问题的原因是因为item中的控件霸占了焦点
也许你会把ImageButton改成ImageView然后给它设一个OnClickListener方法,你会发现ImageView并不会强占焦点。很神奇对不对,我们先看这个问题的解决方法,然后回来再说。
网上的解决方法非常多,基本一致:
- 将ListView中的Item布局中的子控件focusable属性设置为false
- 在getView方法中设置button.setFocusable(false)
- 设置item的根布局的属性android:descendantFocusability=”blocksDescendant”
常用的是第三个,从第二个方法中,我们可以感觉出button的focusable属性应该是true,而普通的控件(如TextView)是false的。
看一下源码吧,Button继承TextView,TextView继承View。看一下View对这几个函数的处理。
public void setFocusable(boolean focusable) {
if (!focusable) {
setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
}
setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK);
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setClickable(boolean clickable) {
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
void setFlags(int flags, int mask) {
final boolean accessibilityEnabled =
AccessibilityManager.getInstance(mContext).isEnabled();
final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
int privateFlags = mPrivateFlags;
/* Check if the FOCUSABLE bit has changed */
if (((changed & FOCUSABLE_MASK) != 0) &&
((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {
if (((old & FOCUSABLE_MASK) == FOCUSABLE)
&& ((privateFlags & PFLAG_FOCUSED) != 0)) {
/* Give up focus if we are no longer focusable */
clearFocus();
} else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
&& ((privateFlags & PFLAG_FOCUSED) == 0)) {
/*
* Tell the view system that we are now available to take focus
* if no one else already has it.
*/
if (mParent != null) mParent.focusableViewAvailable(this);
}
}
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
if ((changed & VISIBILITY_MASK) != 0) {
/*
* If this view is becoming visible, invalidate it in case it changed while
* it was not visible. Marking it drawn ensures that the invalidation will
* go through.
*/
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
// a view becoming visible is worth notifying the parent
// about in case nothing has focus. even if this specific view
// isn't focusable, it may contain something that is, so let
// the root view try to give this focus if nothing else does.
if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
mParent.focusableViewAvailable(this);
}
}
}
/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
needGlobalAttributesUpdate(false);
requestLayout();
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) clearFocus();
clearAccessibilityFocus();
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
// Mark the view drawn to ensure that it gets invalidated properly the next
// time it is visible and gets invalidated
mPrivateFlags |= PFLAG_DRAWN;
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
/* Check if the VISIBLE bit has changed */
if ((changed & INVISIBLE) != 0) {
needGlobalAttributesUpdate(false);
/*
* If this view is becoming invisible, set the DRAWN flag so that
* the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
// root view becoming invisible shouldn't clear focus and accessibility focus
if (getRootView() != this) {
if (hasFocus()) clearFocus();
clearAccessibilityFocus();
}
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
if ((changed & VISIBILITY_MASK) != 0) {
// If the view is invisible, cleanup its display list to free up resources
if (newVisibility != VISIBLE) {
cleanupDraw();
}
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onChildVisibilityChanged(this,
(changed & VISIBILITY_MASK), newVisibility);
((View) mParent).invalidate(true);
} else if (mParent != null) {
mParent.invalidateChild(this, null);
}
dispatchVisibilityChanged(this, newVisibility);
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
destroyDrawingCache();
}
if ((changed & DRAWING_CACHE_ENABLED) != 0) {
destroyDrawingCache();
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
invalidateParentCaches();
}
if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
destroyDrawingCache();
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
}
if ((changed & KEEP_SCREEN_ON) != 0) {
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);
}
}
if (accessibilityEnabled) {
if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0
|| (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0) {
if (oldIncludeForAccessibility != includeForAccessibility()) {
notifySubtreeAccessibilityStateChangedIfNeeded();
} else {
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
} else if ((changed & ENABLED_MASK) != 0) {
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
}
可以看到每个控件都可以设置clickable和focusable,这个问题还是很难解释清楚,openStack有这样的解释
Focusable means ACTION_UP event will occur. It will gain it and it won’t release it.
Clickable means ACTION_UP first and ACTION_DOWN at the end. It will gain and release it at the end.
你可以这样理解,focus占用action,所以click不会接收到action,所以被劫持了。
接下来说另外一个问题。
view这个参数可能我们用的不多,其实view就是我们点击的那个图案,比如一个button或者说一个listview中的item。别忘了控件都是继承view的。而且view有一个tag的属性,这个属性可以通过view来传递一些信息,这个属性尤其有用,特别是在listview中。因为我们总是在adapter中设置点击事件,但是可能具体的实现是在外层中,比如activity。这时点击事件就需要传一些相关信息到外层。比如说这个点击事件所属的某个item的position信息。
我们在adapter中传入位置信息
viewHolder.up.setTag(position);
在外层获得
int position = (Integer)view.getTag()
ok,上代码吧,空听我说可能也不明白什么意思。希望能帮你解决问题,下次将继续解决最后的问题。
代码代码