android按键事件和触屏事件(4)-listview的setOnItemClickListener点击无效的处理

来源:互联网 发布:商场广播软件 编辑:程序博客网 时间:2024/04/30 10:17

在最近的项目中有用到滑动删除,但是会出现这样的问题。滑动删除的item上面的view如果定义的onClickListener此时,我们就会遇到setOnItemClickListener点击无效。当然这是由于item上面的view 消耗了事件。我们可以自定义的ListView 的onInterceptTouchEvent中返回true,将事件拦截掉。子view也就不会得到这个事件了。

还有一种情况就是item上面有一个Button按钮,我们也会遇到setOnItemClickListener点击无效。上百度找到了解决办法有两种,如下: 
1、在checkbox、button对应的view处加android:focusable=”false” 
android:clickable=”false” android:focusableInTouchMode=”false” 
2、在item最外层添加属性 android:descendantFocusability=”blocksDescendants”

网上大多数帖子的理由是:当listview中包含button,checkbox等控件的时候,android会默认将focus给了这些控件,也就是说listview的item根本就获取不到focus,所以导致onitemclick时间不能触发。

我们再来回顾一下android按键事件和触屏事件(一)中的内容:

事件分发重要的三个方法:
1)public boolean dispatchTouchEvent(MotionEvent ev)
该方法用来进行事件分发,在事件传递到当前View的时候调用,返回结果受到当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响。
2)public boolean onInterceptTouchEvent(MotionEvent ev)
该方法在上一个方法dispatchTouchEvent中调用,返回结果表示是否拦截当前事件,默认返回false,也就是不拦截。
3)public void onTouchEvent(MotionEvent event)
在 dispatchTouchEvent方法中调用,该方法用来处理点击事件,返回结果表示是否消耗当前事件。

事件触发的流程:
这里写图片描述


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

@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;            }            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;    }
在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;                if (inList && !child.hasFocusable()) {                    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;         }

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

PerformClick以及相关代码如下:

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()) {                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));                }            }        }    }

第18行代码拿到了我们点击的item View,并且调用了performItemClick方法。我们再来看absListView的performItemClick方法:

@Override    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) {            handled |= super.performItemClick(view, position, id);        }        return handled;    }

看第54行调用了父类的performItemClick方法:

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;    }

好了,搞了半天,终于到点上了。第3

行代码很明显了,就是如果有ItemClickListener,就执行他的onItemClick方法,最终回调到我们常见的那个方法。

到这里,相信大家已经知道,关键代码就是刚才上面我们分析的那一个if判断

if (inList && !child.hasFocusable()) {                    if (mPerformClick == null) {                        mPerformClick = new PerformClick();                    }    .....}
也就是只有item的View hasFocusable( )方法返回false,才会执行onItemClick。

View 和 ViewGroup 的 hasFocusable

ViewGroup的hasFocusable

源码

@Override    public boolean hasFocusable() {        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {            return false;        }        if (isFocusable()) {            return true;        }        final int descendantFocusability = getDescendantFocusability();        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {            final int count = mChildrenCount;            final View[] children = mChildren;            for (int i = 0; i < count; i++) {                final View child = children[i];                if (child.hasFocusable()) {                    return true;                }            }        }        return false;    }

看源码我们可以知道:

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

    再来看View的hasFocusable

public boolean hasFocusable() {        if (!isFocusableInTouchMode()) {            for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {                final ViewGroup g = (ViewGroup) p;                if (g.shouldBlockFocusForTouchscreen()) {                    return false;                }            }        }        return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();    }

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

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

  1. 在checkbox、button对应的view处加android:focusable=”false” 
    android:clickable=”false” android:focusableInTouchMode=”false”

  2. 在item最外层添加属性 android:descendantFocusability=”blocksDescendants”

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

if (inList && !child.hasFocusable()) {                    if (mPerformClick == null) {                        mPerformClick = new PerformClick();                    }    .....}

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

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

好了,分析到这里相信大家已经很明白了。






0 0
原创粉丝点击