前言

最近在做项目的过程中,在使用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去找。还真有:

  1.  
  1. @Override
  2. public boolean onTouchEvent(MotionEvent ev) {
  3. if (!isEnabled()) {
  4. // A disabled view that is clickable still consumes the touch
  5. // events, it just doesn't respond to them.
  6. return isClickable() || isLongClickable();
  7. }
  8.  
  9. if (mPositionScroller != null) {
  10. mPositionScroller.stop();
  11. }
  12.  
  13. if (mIsDetaching || !isAttachedToWindow()) {
  14. // Something isn't right.
  15. // Since we rely on being attached to get data set change notifications,
  16. // don't risk doing anything where we might try to resync and find things
  17. // in a bogus state.
  18. return false;
  19. }
  20.  
  21. startNestedScroll(SCROLL_AXIS_VERTICAL);
  22.  
  23. if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
  24. return true;
  25. }
  26.  
  27. initVelocityTrackerIfNotExists();
  28. final MotionEvent vtev = MotionEvent.obtain(ev);
  29.  
  30. final int actionMasked = ev.getActionMasked();
  31. if (actionMasked == MotionEvent.ACTION_DOWN) {
  32. mNestedYOffset = 0;
  33. }
  34. vtev.offsetLocation(0, mNestedYOffset);
  35. switch (actionMasked) {
  36. case MotionEvent.ACTION_DOWN: {
  37. onTouchDown(ev);
  38. break;
  39. }
  40.  
  41. case MotionEvent.ACTION_MOVE: {
  42. onTouchMove(ev, vtev);
  43. break;
  44. }
  45.  
  46. case MotionEvent.ACTION_UP: {
  47. onTouchUp(ev);
  48. break;
  49. }
  50.  
  51. case MotionEvent.ACTION_CANCEL: {
  52. onTouchCancel();
  53. break;
  54. }
  55.  
  56. case MotionEvent.ACTION_POINTER_UP: {
  57. onSecondaryPointerUp(ev);
  58. final int x = mMotionX;
  59. final int y = mMotionY;
  60. final int motionPosition = pointToPosition(x, y);
  61. if (motionPosition >= 0) {
  62. // Remember where the motion event started
  63. final View child = getChildAt(motionPosition - mFirstPosition);
  64. mMotionViewOriginalTop = child.getTop();
  65. mMotionPosition = motionPosition;
  66. }
  67. mLastY = y;
  68. break;
  69. }
  70.  
  71. case MotionEvent.ACTION_POINTER_DOWN: {
  72. // New pointers take over dragging duties
  73. final int index = ev.getActionIndex();
  74. final int id = ev.getPointerId(index);
  75. final int x = (int) ev.getX(index);
  76. final int y = (int) ev.getY(index);
  77. mMotionCorrection = 0;
  78. mActivePointerId = id;
  79. mMotionX = x;
  80. mMotionY = y;
  81. final int motionPosition = pointToPosition(x, y);
  82. if (motionPosition >= 0) {
  83. // Remember where the motion event started
  84. final View child = getChildAt(motionPosition - mFirstPosition);
  85. mMotionViewOriginalTop = child.getTop();
  86. mMotionPosition = motionPosition;
  87. }
  88. mLastY = y;
  89. break;
  90. }
  91. }
  92.  
  93. if (mVelocityTracker != null) {
  94. mVelocityTracker.addMovement(vtev);
  95. }
  96. vtev.recycle();
  97. return true;
  98. }

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

  1. private void onTouchUp(MotionEvent ev) {
  2. switch (mTouchMode) {
  3. case TOUCH_MODE_DOWN:
  4. case TOUCH_MODE_TAP:
  5. case TOUCH_MODE_DONE_WAITING:
  6. final int motionPosition = mMotionPosition;
  7. final View child = getChildAt(motionPosition - mFirstPosition);
  8. if (child != null) {
  9. if (mTouchMode != TOUCH_MODE_DOWN) {
  10. child.setPressed(false);
  11. }
  12.  
  13. final float x = ev.getX();
  14. final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
  15. if (inList && !child.hasFocusable()) {
  16. if (mPerformClick == null) {
  17. mPerformClick = new PerformClick();
  18. }
  19.  
  20. final AbsListView.PerformClick performClick = mPerformClick;
  21. performClick.mClickMotionPosition = motionPosition;
  22. performClick.rememberWindowAttachCount();
  23.  
  24. mResurrectToPosition = motionPosition;
  25.  
  26. if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
  27. removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
  28. mPendingCheckForTap : mPendingCheckForLongPress);
  29. mLayoutMode = LAYOUT_NORMAL;
  30. if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  31. mTouchMode = TOUCH_MODE_TAP;
  32. setSelectedPositionInt(mMotionPosition);
  33. layoutChildren();
  34. child.setPressed(true);
  35. positionSelector(mMotionPosition, child);
  36. setPressed(true);
  37. if (mSelector != null) {
  38. Drawable d = mSelector.getCurrent();
  39. if (d != null && d instanceof TransitionDrawable) {
  40. ((TransitionDrawable) d).resetTransition();
  41. }
  42. mSelector.setHotspot(x, ev.getY());
  43. }
  44. if (mTouchModeReset != null) {
  45. removeCallbacks(mTouchModeReset);
  46. }
  47. mTouchModeReset = new Runnable() {
  48. @Override
  49. public void run() {
  50. mTouchModeReset = null;
  51. mTouchMode = TOUCH_MODE_REST;
  52. child.setPressed(false);
  53. setPressed(false);
  54. if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
  55. performClick.run();
  56. }
  57. }
  58. };
  59. postDelayed(mTouchModeReset,
  60. ViewConfiguration.getPressedStateDuration());
  61. } else {
  62. mTouchMode = TOUCH_MODE_REST;
  63. updateSelectorState();
  64. }
  65. return;
  66. } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  67. performClick.run();
  68. }
  69. }
  70. }
  71. mTouchMode = TOUCH_MODE_REST;
  72. updateSelectorState();
  73. break;

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

PerformClick以及相关代码如下:

  1. private class PerformClick extends WindowRunnnable implements Runnable {
  2. int mClickMotionPosition;
  3.  
  4. @Override
  5. public void run() {
  6. // The data has changed since we posted this action in the event queue,
  7. // bail out before bad things happen
  8. if (mDataChanged) return;
  9.  
  10. final ListAdapter adapter = mAdapter;
  11. final int motionPosition = mClickMotionPosition;
  12. if (adapter != null && mItemCount > 0 &&
  13. motionPosition != INVALID_POSITION &&
  14. motionPosition < adapter.getCount() && sameWindow()) {
  15. final View view = getChildAt(motionPosition - mFirstPosition);
  16. // If there is no view, something bad happened (the view scrolled off the
  17. // screen, etc.) and we should cancel the click
  18. if (view != null) {
  19. performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
  20. }
  21. }
  22. }
  23. }

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

  1. @Override
  2. public boolean performItemClick(View view, int position, long id) {
  3. boolean handled = false;
  4. boolean dispatchItemClick = true;
  5.  
  6. if (mChoiceMode != CHOICE_MODE_NONE) {
  7. handled = true;
  8. boolean checkedStateChanged = false;
  9.  
  10. if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
  11. (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
  12. boolean checked = !mCheckStates.get(position, false);
  13. mCheckStates.put(position, checked);
  14. if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
  15. if (checked) {
  16. mCheckedIdStates.put(mAdapter.getItemId(position), position);
  17. } else {
  18. mCheckedIdStates.delete(mAdapter.getItemId(position));
  19. }
  20. }
  21. if (checked) {
  22. mCheckedItemCount++;
  23. } else {
  24. mCheckedItemCount--;
  25. }
  26. if (mChoiceActionMode != null) {
  27. mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
  28. position, id, checked);
  29. dispatchItemClick = false;
  30. }
  31. checkedStateChanged = true;
  32. } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
  33. boolean checked = !mCheckStates.get(position, false);
  34. if (checked) {
  35. mCheckStates.clear();
  36. mCheckStates.put(position, true);
  37. if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
  38. mCheckedIdStates.clear();
  39. mCheckedIdStates.put(mAdapter.getItemId(position), position);
  40. }
  41. mCheckedItemCount = 1;
  42. } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
  43. mCheckedItemCount = 0;
  44. }
  45. checkedStateChanged = true;
  46. }
  47.  
  48. if (checkedStateChanged) {
  49. updateOnScreenCheckedViews();
  50. }
  51. }
  52.  
  53. if (dispatchItemClick) {
  54. handled |= super.performItemClick(view, position, id);
  55. }
  56.  
  57. return handled;
  58. }

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

  1. public boolean performItemClick(View view, int position, long id) {
  2. final boolean result;
  3. if (mOnItemClickListener != null) {
  4. playSoundEffect(SoundEffectConstants.CLICK);
  5. mOnItemClickListener.onItemClick(this, view, position, id);
  6. result = true;
  7. } else {
  8. result = false;
  9. }
  10.  
  11. if (view != null) {
  12. view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  13. }
  14. return result;
  15. }

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

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

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

  1. if (inList && !child.hasFocusable()) {
  2. if (mPerformClick == null) {
  3. mPerformClick = new PerformClick();
  4. }
  5. .....

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

View 和 ViewGroup 的 hasFocusable

ViewGroup的hasFocusable

源码

  1. @Override
  2. public boolean hasFocusable() {
  3. if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
  4. return false;
  5. }
  6.  
  7. if (isFocusable()) {
  8. return true;
  9. }
  10.  
  11. final int descendantFocusability = getDescendantFocusability();
  12. if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
  13. final int count = mChildrenCount;
  14. final View[] children = mChildren;
  15.  
  16. for (int i = 0; i < count; i++) {
  17. final View child = children[i];
  18. if (child.hasFocusable()) {
  19. return true;
  20. }
  21. }
  22. }
  23.  
  24. return false;
  25. }

看源码我们可以知道:

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

    再来看View的hasFocusable

    ViewGroup的hasFocusable

  1. public boolean hasFocusable() {
  2. if (!isFocusableInTouchMode()) {
  3. for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
  4. final ViewGroup g = (ViewGroup) p;
  5. if (g.shouldBlockFocusForTouchscreen()) {
  6. return false;
  7. }
  8. }
  9. }
  10. return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();
  11. }
  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也没有获取焦点,那么上面说到回调至性的条件判断也就的代码:

  1. if (inList && !child.hasFocusable()) {
  2. if (mPerformClick == null) {
  3. mPerformClick = new PerformClick();
  4. }
  5. .....

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

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

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

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

ListView setOnItemClickListener无效原因分析的更多相关文章

  1. ListView setOnItemClickListener无效原因具体分析

    前言 近期在做项目的过程中,在使用listview的时候遇到了设置item监听事件的时候在没有回调onItemClick 方法的问题. 我的情况是在item中有一个Buttonbutton. 所以不会 ...

  2. ListView.setOnItemClickListener无效

    如果ListView中的单个Item的view中存在checkbox,button等view,会导致ListView.setOnItemClickListener无效, 事件会被子View捕获到,Li ...

  3. div层调整zindex属性无效原因分析及解决方法

    在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z- index,要让z-index起作用有个小小前提,就是元素的po ...

  4. 【转载】div层调整zindex属性无效原因分析及解决方法

    在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z-index,要让z-index起作用有个小小前提,就是元素的pos ...

  5. 【Web前端】div层调整zindex属性无效原因分析及解决方法

    在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z- index,要让z-index起作用有个小小前提,就是元素的po ...

  6. ListView.setOnItemClickListener 点击无效

    如果ListView中的单个Item的view中存在checkbox,button等view,会导致ListView.setOnItemClickListener无效, 事件会被子View捕获到,Li ...

  7. Android ListView异步载入图片乱序问题,原因分析及解决方式

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android全部系统自带的控件其中,ListView这个控件算是 ...

  8. mysql索引无效且sending data耗时巨大原因分析

    一朋友最近新上线一个项目,本地测试环境跑得好好的,部署到线上却慢得像蜗牛一样.后来查询了一下发现一个sql执行了16秒,有些长的甚至80秒.本地运行都是毫秒级别的查询.下面记录一下困扰了两天的,其中一 ...

  9. ListView+CheckBox两种解决方式及原因分析

    近期在用ListView+CheckBox搞一个item选中的项目,我将CheckBox的focus设置为false,另我大喜的是,CheckBox居然能够选中(窃喜中),这么简单就搞定了,由于数据量 ...

随机推荐

  1. android给View设置上下左右边框

    给View控件设置边框,可以动态设置上下左右.通过布局文件就能搞定 1.在drawable文件夹下新建一个shape_main_list_bg.xml文件 <layer-list xmlns:a ...

  2. iOS开发之使用CocoaPods更新第三方出现“target overrides the `OTHER_LDFLAGS`……”问题解决方案

    今天在自己的项目中用CocoaPods引入第三方SDWebImage的时候,出现了问题.当更新完毕后,在终端没太注意这个问题的提示,就直接使用SDWebImage了,在使用的时候一些方法的提示和头文件 ...

  3. Linux笔记之——Linux关机命令详解(转)

    原文连接:http://www.jb51.net/os/RedHat/1334.html 在linux下一些常用的关机/重启命令有shutdown.halt.reboot.及init,它们都可以达到重 ...

  4. 扩展KMP算法

    一 问题定义 给定母串S和子串T,定义n为母串S的长度,m为子串T的长度,suffix[i]为第i个字符开始的母串S的后缀子串,extend[i]为suffix[i]与字串T的最长公共前缀长度.求出所 ...

  5. geotrellis使用(十)缓冲区分析以及多种类型要素栅格化

    目录 前言 缓冲区分析 多种类型要素栅格化 总结 参考链接 一.前言        上两篇文章介绍了如何使用Geotrellis进行矢量数据栅格化以及栅格渲染,本文主要介绍栅格化过程中常用到的缓冲区分 ...

  6. ios如何在#import方面提升编译性能

    模块的使用非常简单,对于存在的工程,第一件事情就是让这个功能生效.可以在项目的Build Settings 中搜索Modules 找到这个选项,做以下的设置 默认的情况下都是开启的 对于系统自带的只需 ...

  7. 设置SharePoint Server 2013 的匿名访问

    默认情况下,SharePoint Server 2013 是关闭匿名访问的,但是某些环境下我们又需要将这个匿名访问对全员开放,怎么操作更加安全与便捷,对于一个崭新的环境我们可以这样操作. 首先在当前需 ...

  8. Oracle库Delete删除千万以上普通堆表数据的方法

    需求:Oracle数据库delete删除普通堆表千万条历史记录. 直接删除的影响: 1.可能由于undo表空间不足从而导致最终删除失败的问题: 2.可能导致undo表空间过度使用,影响到其他用户正常操 ...

  9. SQL Server SQL性能优化之--数据库在“简单”参数化模式下,自动参数化SQL带来的问题

    数据库参数化的模式 数据库的参数化有两种方式,简单(simple)和强制(forced),默认的参数化默认是“简单”,简单模式下,如果每次发过来的SQL,除非完全一样,否则就重编译它(特殊情况会自动参 ...

  10. nodejs学习笔记一——nodejs安装

    a.nodejs安装 nodejs的安装没有什么说的默认安装即可.安装包官网下载即可:nodejs官网 本人用的是window的安装包node-v4.2.6-x64.msi 安装完成后打开命令行查看使 ...