继上一篇处理ListView嵌套EditText出现的问题,有一个问题EditText使ListView的setOnItemClickListener回调失效,最新看了源码,得出了一些结论,如有错误欢迎指出,不需要看完整代码,看标记的最关键的就行。
1.我们来一步步看这个回调过程
首先我们看到,这个回调发生在ListView的父类AdapterView的performItemClick中,下面是源码
/**
* Call the OnItemClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @param view The view within the AdapterView that was clicked.
* @param position The position of the view in the adapter.
* @param id The row id of the item that was clicked.
* @return True if there was an assigned OnItemClickListener that was
* called, false otherwise is returned.
*/
public boolean performItemClick(View view, int position, long id) {
final boolean result;
if (mOnItemClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnItemClickListener.onItemClick(this, view, position, id);
result = true;
} else {
result = false;
}
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
return result;
}
2.我们再来看是哪里调用了performItemClick,可以看到是在AbsListView中
private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition;
@Override
public void run() {
// The data has changed since we posted this action in the event queue,
// bail out before bad things happen
if (mDataChanged) return;
final ListAdapter adapter = mAdapter;
final int motionPosition = mClickMotionPosition;
if (adapter != null && mItemCount > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow() &&
adapter.isEnabled(motionPosition)) {
final View view = getChildAt(motionPosition - mFirstPosition);
// If there is no view, something bad happened (the view scrolled off the
// screen, etc.) and we should cancel the click
if (view != null) {
//这里这里这里!!!!!
performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
}
}
}
}
3.当然这里是利用线程,然后再看在哪里启动的,结果还是在这个类里面
private void onTouchUp(MotionEvent ev) {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
final int motionPosition = mMotionPosition;
final View child = getChildAt(motionPosition - mFirstPosition);
if (child != null) {
if (mTouchMode != TOUCH_MODE_DOWN) {
child.setPressed(false);
}
final float x = ev.getX();
final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
//!!!!!!!!!!
if (inList && !child.hasExplicitFocusable()) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
final AbsListView.PerformClick performClick = mPerformClick;
performClick.mClickMotionPosition = motionPosition;
performClick.rememberWindowAttachCount();
mResurrectToPosition = motionPosition;
if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
mPendingCheckForTap : mPendingCheckForLongPress);
mLayoutMode = LAYOUT_NORMAL;
if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
mTouchMode = TOUCH_MODE_TAP;
setSelectedPositionInt(mMotionPosition);
layoutChildren();
child.setPressed(true);
positionSelector(mMotionPosition, child);
setPressed(true);
if (mSelector != null) {
Drawable d = mSelector.getCurrent();
if (d != null && d instanceof TransitionDrawable) {
((TransitionDrawable) d).resetTransition();
}
mSelector.setHotspot(x, ev.getY());
}
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
}
mTouchModeReset = new Runnable() {
@Override
public void run() {
mTouchModeReset = null;
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
}
};
postDelayed(mTouchModeReset,
ViewConfiguration.getPressedStateDuration());
} else {
mTouchMode = TOUCH_MODE_REST;
updateSelectorState();
}
return;
} else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
//线程在这里被启动!!!!!!!!!!!
performClick.run();
}
}
}
mTouchMode = TOUCH_MODE_REST;
updateSelectorState();
break;
上面的代码没有截取完整的,但是这部分就够了,实验发现,问题出在了上面第一次的连续!标记处,即
inList && !child.hasExplicitFocusable(),根据debug发现inList为true,所以问题在右边 !child.hasExplicitFocusable()为false造成下面的代码没执行,performclick没有启动,也就是child.hasExplicitFocusable()为true,这个方法代码如下
只要自己或自己里面的的View有明确的可获得焦点的都会返回true
/**
* Returns true if this view is focusable or if it contains a reachable View
* for which {@link #hasExplicitFocusable()} returns {@code true}.
* A "reachable hasExplicitFocusable()" is a view whose parents do not block descendants focus.
* Only {@link #VISIBLE} views for which {@link #getFocusable()} would return
* {@link #FOCUSABLE} are considered focusable.
*
* <p>This method preserves the pre-{@link Build.VERSION_CODES#O} behavior of
* {@link #hasFocusable()} in that only views explicitly set focusable will cause
* this method to return true. A view set to {@link #FOCUSABLE_AUTO} that resolves
* to focusable will not.</p>
*
* @return {@code true} if the view is focusable or if the view contains a focusable
* view, {@code false} otherwise
*
* @see #hasFocusable()
*/
public boolean hasExplicitFocusable() {
return hasFocusable(false, true);
}
boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
if (!isFocusableInTouchMode()) {
for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
final ViewGroup g = (ViewGroup) p;
if (g.shouldBlockFocusForTouchscreen()) {
return false;
}
}
}
// Invisible and gone views are never focusable.
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
//debug发现最终代码执行到了这个if语句里面,说明这个if是成立的
// Only use effective focusable value when allowed.
if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
return true;
}
return false;
}
4.根据文档说明
if the view is focusable or if the view contains a focusable
view, {@code false} otherwise
意思是 如果这个View是可获得焦点或者它包含一个可获得焦点的View就返回True,经过验证EditText是默认可获得焦点的,这就使方法返回true,所以child.hasExplicitFocusable()为true,所以造成回调无法被执行。
这个回调可以看到是在onTouchUp中被调用,而onTouchUp是在OnTouchEvent中,接收到事件ACTION_UP时候被调用
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled()) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return isClickable() || isLongClickable();
}
if (mPositionScroller != null) {
mPositionScroller.stop();
}
if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right.
// Since we rely on being attached to get data set change notifications,
// don't risk doing anything where we might try to resync and find things
// in a bogus state.
return false;
}
startNestedScroll(SCROLL_AXIS_VERTICAL);
if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
return true;
}
initVelocityTrackerIfNotExists();
final MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
onTouchDown(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev, vtev);
break;
}
/** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!这里 */
case MotionEvent.ACTION_UP: {
onTouchUp(ev);
break;
}
通过给Item的外层ViewGroup添加OnClick,根据事件分发规则,内部的item会优先处理事件,不能处理的再抛给父控件,
所以onclick可以响应回调,然后再把事件抛给ListView,只是ListView会消耗事件(onTouchEvent返回true) 但是不会处理点击回调
分析到这里就结束了,涉及的类不是很多,大家只需要看最关键的几个方法就行,如果有不对欢迎指出