当ListView中包含Button、CheckBox等控件的时候,Android会默认将焦点给了这些控件,也就是说ListView的item根本就获取不到焦点,所以导致onItemClick时间不能触发。今天得空,做一个简单分析。
刚才百度了一下,找到两种解决方法,如下:

1、在Checkbox、Button对应的View处加 android:focusable="false"
2、在item根布局添加属性 android:descendantFocusability="blocksDescendants"

要搞清楚这个问题,要对Android事件分发机制有一定的了解,事件分发机制网上有大神写了一些特别详细和优秀的文章,在这里就不做介绍了,当用户点击事件出发后的流程,如下:

了解事件分发机制之后,我们在setOnItemClick之后肯定需要进行事件处理,上面说到事件拦截默认是不拦截,所以我们猜想会到ListView的onTouchEvent方法中去处理ItemClick事件。去找你会发现ListView没有onTouchEvent方法。那我们再去他的父类AbsListView去找。还真有:

  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;            }        /**         *  重点1:         */            case MotionEvent.ACTION_UP: {                onTouchUp(ev);                break;            }            case MotionEvent.ACTION_CANCEL: {                onTouchCancel();                break;            }            case MotionEvent.ACTION_POINTER_UP: {                onSecondaryPointerUp(ev);                final int x = mMotionX;                final int y = mMotionY;                final int motionPosition = pointToPosition(x, y);                if (motionPosition >= 0) {                    // Remember where the motion event started                    final View child = getChildAt(motionPosition - mFirstPosition);                    mMotionViewOriginalTop = child.getTop();                    mMotionPosition = motionPosition;                }                mLastY = y;                break;            }            case MotionEvent.ACTION_POINTER_DOWN: {                // New pointers take over dragging duties                final int index = ev.getActionIndex();                final int id = ev.getPointerId(index);                final int x = (int) ev.getX(index);                final int y = (int) ev.getY(index);                mMotionCorrection = 0;                mActivePointerId = id;                mMotionX = x;                mMotionY = y;                final int motionPosition = pointToPosition(x, y);                if (motionPosition >= 0) {                    // Remember where the motion event started                    final View child = getChildAt(motionPosition - mFirstPosition);                    mMotionViewOriginalTop = child.getTop();                    mMotionPosition = motionPosition;                }                mLastY = y;                break;            }        }        if (mVelocityTracker != null) {            mVelocityTracker.addMovement(vtev);        }        vtev.recycle();        return true;    }

代码比较长,我们主要看标识重点1部分 MotionEvent.ACTION_UP的情况,因为onItemClick事件的触发是在我们的手指从屏幕抬起的那一刻,在MotionEvent.ACTION_UP的情况下执行了onTouchUp(ev);那么我们可以想到问题发生的原因应该就是在这个方法了里了。

   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;                 /**                  *  重点2:                  */                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;        case TOUCH_MODE_SCROLL:            final int childCount = getChildCount();            if (childCount > 0) {                final int firstChildTop = getChildAt(0).getTop();                final int lastChildBottom = getChildAt(childCount - 1).getBottom();                final int contentTop = mListPadding.top;                final int contentBottom = getHeight() - mListPadding.bottom;                if (mFirstPosition == 0 && firstChildTop >= contentTop &&                        mFirstPosition + childCount < mItemCount &&                        lastChildBottom <= getHeight() - contentBottom) {                    mTouchMode = TOUCH_MODE_REST;                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);                } else {                    final VelocityTracker velocityTracker = mVelocityTracker;                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);                    final int initialVelocity = (int)                            (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);                    // Fling if we have enough velocity and we aren't at a boundary.                    // Since we can potentially overfling more than we can overscroll, don't                    // allow the weird behavior where you can scroll to a boundary then                    // fling further.                    boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;                    if (flingVelocity &&                            !((mFirstPosition == 0 &&                                    firstChildTop == contentTop - mOverscrollDistance) ||                              (mFirstPosition + childCount == mItemCount &&                                    lastChildBottom == contentBottom + mOverscrollDistance))) {                        if (!dispatchNestedPreFling(0, -initialVelocity)) {                            if (mFlingRunnable == null) {                                mFlingRunnable = new FlingRunnable();                            }                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);                            mFlingRunnable.start(-initialVelocity);                            dispatchNestedFling(0, -initialVelocity, true);                        } else {                            mTouchMode = TOUCH_MODE_REST;                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);                        }                    } else {                        mTouchMode = TOUCH_MODE_REST;                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);                        if (mFlingRunnable != null) {                            mFlingRunnable.endFling();                        }                        if (mPositionScroller != null) {                            mPositionScroller.stop();                        }                        if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {                            dispatchNestedFling(0, -initialVelocity, false);                        }                    }                }            } else {                mTouchMode = TOUCH_MODE_REST;                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);            }            break;        case TOUCH_MODE_OVERSCROLL:            if (mFlingRunnable == null) {                mFlingRunnable = new FlingRunnable();            }            final VelocityTracker velocityTracker = mVelocityTracker;            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);            final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);            if (Math.abs(initialVelocity) > mMinimumVelocity) {                mFlingRunnable.startOverfling(-initialVelocity);            } else {                mFlingRunnable.startSpringback();            }            break;        }        setPressed(false);        if (mEdgeGlowTop != null) {            mEdgeGlowTop.onRelease();            mEdgeGlowBottom.onRelease();        }        // Need to redraw since we probably aren't drawing the selector anymore        invalidate();        removeCallbacks(mPendingCheckForLongPress);        recycleVelocityTracker();        mActivePointerId = INVALID_POINTER;        if (PROFILE_SCROLLING) {            if (mScrollProfilingStarted) {                Debug.stopMethodTracing();                mScrollProfilingStarted = false;            }        }        if (mScrollStrictSpan != null) {            mScrollStrictSpan.finish();            mScrollStrictSpan = null;        }    }

这里主要看上面的重点2,拿到了我们item的View,并且判断了item的View是否在范围是否获取焦点(hasFocusable()),这里对hasFocusable()取反判断,也就是说,必需要我们的itemView的hasFocusable() 方法返回false, 才会执行一下的方法,以下的方法就是点击事件的方法。那么我们来看看是不是mPerformClick真的就是执行我们的itemClick事件。

  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) {                 /**                  *  重点3:                  */                    performItemClick(view, motionPosition, adapter.getItemId(motionPosition));                }            }        }    }

重点3拿到了我们点击的item View,并且调用了performItemClick方法。我们再来看absListView的performItemClick方法:

    public boolean performItemClick(View view, int position, long id) {        boolean handled = false;        boolean dispatchItemClick = true;        if (mChoiceMode != CHOICE_MODE_NONE) {            handled = true;            boolean checkedStateChanged = false;            if (mChoiceMode == CHOICE_MODE_MULTIPLE ||                    (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {                boolean checked = !mCheckStates.get(position, false);                mCheckStates.put(position, checked);                if (mCheckedIdStates != null && mAdapter.hasStableIds()) {                    if (checked) {                        mCheckedIdStates.put(mAdapter.getItemId(position), position);                    } else {                        mCheckedIdStates.delete(mAdapter.getItemId(position));                    }                }                if (checked) {                    mCheckedItemCount++;                } else {                    mCheckedItemCount--;                }                if (mChoiceActionMode != null) {                    mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,                            position, id, checked);                    dispatchItemClick = false;                }                checkedStateChanged = true;            } else if (mChoiceMode == CHOICE_MODE_SINGLE) {                boolean checked = !mCheckStates.get(position, false);                if (checked) {                    mCheckStates.clear();                    mCheckStates.put(position, true);                    if (mCheckedIdStates != null && mAdapter.hasStableIds()) {                        mCheckedIdStates.clear();                        mCheckedIdStates.put(mAdapter.getItemId(position), position);                    }                    mCheckedItemCount = 1;                } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {                    mCheckedItemCount = 0;                }                checkedStateChanged = true;            }            if (checkedStateChanged) {                updateOnScreenCheckedViews();            }        }        if (dispatchItemClick) {              /**                *  重点4:                 */            handled |= super.performItemClick(view, position, id);        }        return handled;    }

重点4调用了父类的performItemClick方法:

    public boolean performItemClick(View view, int position, long id) {        final boolean result;        if (mOnItemClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);           /**            *  重点5:            */            mOnItemClickListener.onItemClick(this, view, position, id);            result = true;        } else {            result = false;        }        if (view != null) {            view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        }        return result;    }

好了,搞了半天,终于到点上了。
重点5很明显了,就是如果有ItemClickListener,就执行他的onItemClick方法,最终回调到我们常见的那个方法。
到这里,相信大家已经知道,关键代码就是刚才上面我们分析的那一个if判断

也就是只有item的View hasFocusable( )方法返回false,才会执行onItemClick。

View 和 ViewGroup 的 hasFocusable

1、ViewGroup的hasFocusable

    boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {        // This should probably be super.hasFocusable, but that would change        // behavior. Historically, we have not checked the ancestor views for        // shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.        // Invisible and gone views are never focusable.        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {            return false;        }        // Only use effective focusable value when allowed.        if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {            return true;        }        // Determine whether we have a focused descendant.        final int descendantFocusability = getDescendantFocusability();        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {            return hasFocusableChild(dispatchExplicit);        }        return false;    }    boolean hasFocusableChild(boolean dispatchExplicit) {        // Determine whether we have a focusable descendant.        final int count = mChildrenCount;        final View[] children = mChildren;        for (int i = 0; i < count; i++) {            final View child = children[i];            // In case the subclass has overridden has[Explicit]Focusable, dispatch            // to the expected one for each child even though we share logic here.            if ((dispatchExplicit && child.hasExplicitFocusable())                    || (!dispatchExplicit && child.hasFocusable())) {                return true;            }        }        return false;    }

如果 ViewGroup visiable 和 focusable 都为 true,就算能够获取焦点, 返回 true。
如果我们给ViewGroup设置了descendantFocusability属性,并且等于FOCUS_BLOCK_DESCENDANTS的情况下,返回false。不能获取焦点。
如果没有设置descendantFocusability属性的话,只要一个子View hasFocusable返回了true,ViewGroup的hasFocusable就返回。

2、View的hasFocusable

    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;        }        // Only use effective focusable value when allowed.        if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {            return true;        }        return false;    }

在触摸模式下如果不可获取焦点,先遍历 View 的所有父节点,如果有一个父节点设置了阻塞子 View 获取焦点,那么该 View 就不可能获取焦点
在触摸模式下如果不可获取焦点,并且没有父节点设置阻塞子 View 获取焦点,和在触摸模式下如果可以获取焦点,那么才判断 View 自身的 visiable 和 focusable 属性,来决定是否可以获取焦点,只有 visiable 和 focusable 同时为 true,该View 才可能获取焦点。

好了,分析到这里我们再回过头去看两个解决办法。

第一种情况,item没有设置descendantFocusability=”blocksDescendants”,遍历了所有子View,由于所有的子view都不可获得焦点,所有item也没有获取焦点,那么上面说到回调至性的条件判断也就的代码:

if条件成立,所有执行了回调。

第二种情况,item设置了descendantFocusability=”blocksDescendants”,所有没有遍历子 View,child.hasFocusable(),直接返回false了。

更多相关文章

  1. android图片压缩终极解决方案
  2. [Android]通过剪切板实现Activity之间传递数据
  3. Android(安卓)Studio创建AIDL文件并实现进程间通讯实例
  4. android学习笔记1——webview相关
  5. Qt on Android:使用JNI与第三方jar包
  6. Android(安卓)从程序启动startActivity到生命周期变化的过程
  7. Android之串口通信基础
  8. (4.1.19)【Android(安卓)UI设计与开发】第06期:底部菜单栏(一)使用Tab
  9. Android(安卓)手机获取手机号实现方法

随机推荐

  1. android layout_weight的理解
  2. android的ListView自定义滚动条
  3. Android之获取、设置EditText的文本...
  4. android布局相关
  5. Android通过HTTP协议实现多线程下载
  6. Android 4.0
  7. 使用Android自带Gallery组件实现CoverFlo
  8. 如何android多Activity间共享数据 (extend
  9. android应用发短信
  10. android上用opengl画线