博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ListView setOnItemClickListener无效原因分析
阅读量:6827 次
发布时间:2019-06-26

本文共 13672 字,大约阅读时间需要 45 分钟。

前言

近期在做项目的过程中,在使用listview的时候遇到了设置item监听事件的时候在没有回调onItemClick 方法的问题。我的情况是在item中有一个Button按钮。所以不会回调。上百度找到了解决的方法有两种,例如以下:

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时间不能触发

因为自己想去验证一下。全部有了这篇文章。

好了以下開始

我们为ListView设置的onItemClickListener是在何处回调的?

要搞清楚这个问题,我们先从 android事件分发机制開始说起。事件分发机制网上有大神写了一些特别具体和优秀的文章,在这里就仅仅做简要介绍了:

事件分发重要的三个方法

public boolean dispatchTouchEvent(MotionEvent ev)

该方法用来进行事件分发,在事件传递到当前View的时候调用,返回结果受到当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响。

public boolean onInterceptTouchEvent(MotionEvent ev)

该方法在上一个方法dispatchTouchEvent中调用,返回结果表示是否拦截当前事件,默认返回false,也就是不拦截。

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

代码比較长。我们主要看46行 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;                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

    ViewGroup的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了。

好了,分析到这里相信大家已经非常明确了。

如有对你有帮助。请各位大侠点以下的评论或点赞。如有错误请轻喷。。。。

你可能感兴趣的文章
13. 关于IDEA工具在springboot整合mybatis中出现的Invalid bound statement (not found)问题
查看>>
mysql监测工具
查看>>
lxml xpath 爬取并正常显示中文内容
查看>>
boost bind使用指南
查看>>
Centos防火墙设置与端口开放的方法
查看>>
工作总结 razor 接收datatable
查看>>
[leetcode]Unique Paths II
查看>>
C#调用dll时的类型转换总结
查看>>
在线预览Word,Excel
查看>>
工作总结 页面通过ajax 动态绑定 列表页面 列表每一项的事件 事件触发多次...
查看>>
备忘录模式
查看>>
图的基本概念
查看>>
Hibernate学习(1)简单介绍
查看>>
bootstrap-fileinput上传文件的插件使用总结----编辑已成功上传过的图片
查看>>
leetcode 762. Prime Number of Set Bits in Binary Representation
查看>>
Exception loading sessions from persistent storage 这个问题的解决
查看>>
pom.xml 配置maven私服
查看>>
python dns server开源列表 TODO
查看>>
Go中的make和new的区别
查看>>
javascript 面向对象编程(工厂模式、构造函数模式、原型模式)
查看>>