ListView setOnItemClickListener无效原因分析
前言
最近在做项目的过程中,在使用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;
- }
看源码我们可以知道:
- 如果 ViewGroup visiable 和 focusable 都为 true,就算能够获取焦点, 返回 true。
- 如果我们给ViewGroup设置了descendantFocusability属性,并且等于FOCUS_BLOCK_DESCENDANTS的情况下,返回false。不能获取焦点。
如果没有设置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();
- }
- 在触摸模式下如果不可获取焦点,先遍历 View 的所有父节点,如果有一个父节点设置了阻塞子 View 获取焦点,那么该 View 就不可能获取焦点
- 在触摸模式下如果不可获取焦点,并且没有父节点设置阻塞子 View 获取焦点,和在触摸模式下如果可以获取焦点,那么才判断 View 自身的 visiable 和 focusable 属性,来决定是否可以获取焦点,只有 visiable 和 focusable 同时为 true,该View 才可能获取焦点。
好了,分析到这里我们再回过头去看两个解决办法。
在checkbox、button对应的view处加android:focusable=”false”
android:clickable=”false” android:focusableInTouchMode=”false”在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了。
好了,分析到这里相信大家已经很明白了。
如有对你有帮助,请各位大侠点下面的评论或点赞。如有错误请轻喷。。。。
ListView setOnItemClickListener无效原因分析的更多相关文章
- ListView setOnItemClickListener无效原因具体分析
前言 近期在做项目的过程中,在使用listview的时候遇到了设置item监听事件的时候在没有回调onItemClick 方法的问题. 我的情况是在item中有一个Buttonbutton. 所以不会 ...
- ListView.setOnItemClickListener无效
如果ListView中的单个Item的view中存在checkbox,button等view,会导致ListView.setOnItemClickListener无效, 事件会被子View捕获到,Li ...
- div层调整zindex属性无效原因分析及解决方法
在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z- index,要让z-index起作用有个小小前提,就是元素的po ...
- 【转载】div层调整zindex属性无效原因分析及解决方法
在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z-index,要让z-index起作用有个小小前提,就是元素的pos ...
- 【Web前端】div层调整zindex属性无效原因分析及解决方法
在做的过程中,发现了一个很简单却又很多人应该碰到的问题,设置Z-INDEX属性无效.在CSS中,只能通过代码改变层级,这个属性就是z- index,要让z-index起作用有个小小前提,就是元素的po ...
- ListView.setOnItemClickListener 点击无效
如果ListView中的单个Item的view中存在checkbox,button等view,会导致ListView.setOnItemClickListener无效, 事件会被子View捕获到,Li ...
- Android ListView异步载入图片乱序问题,原因分析及解决方式
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android全部系统自带的控件其中,ListView这个控件算是 ...
- mysql索引无效且sending data耗时巨大原因分析
一朋友最近新上线一个项目,本地测试环境跑得好好的,部署到线上却慢得像蜗牛一样.后来查询了一下发现一个sql执行了16秒,有些长的甚至80秒.本地运行都是毫秒级别的查询.下面记录一下困扰了两天的,其中一 ...
- ListView+CheckBox两种解决方式及原因分析
近期在用ListView+CheckBox搞一个item选中的项目,我将CheckBox的focus设置为false,另我大喜的是,CheckBox居然能够选中(窃喜中),这么简单就搞定了,由于数据量 ...
随机推荐
- android给View设置上下左右边框
给View控件设置边框,可以动态设置上下左右.通过布局文件就能搞定 1.在drawable文件夹下新建一个shape_main_list_bg.xml文件 <layer-list xmlns:a ...
- iOS开发之使用CocoaPods更新第三方出现“target overrides the `OTHER_LDFLAGS`……”问题解决方案
今天在自己的项目中用CocoaPods引入第三方SDWebImage的时候,出现了问题.当更新完毕后,在终端没太注意这个问题的提示,就直接使用SDWebImage了,在使用的时候一些方法的提示和头文件 ...
- Linux笔记之——Linux关机命令详解(转)
原文连接:http://www.jb51.net/os/RedHat/1334.html 在linux下一些常用的关机/重启命令有shutdown.halt.reboot.及init,它们都可以达到重 ...
- 扩展KMP算法
一 问题定义 给定母串S和子串T,定义n为母串S的长度,m为子串T的长度,suffix[i]为第i个字符开始的母串S的后缀子串,extend[i]为suffix[i]与字串T的最长公共前缀长度.求出所 ...
- geotrellis使用(十)缓冲区分析以及多种类型要素栅格化
目录 前言 缓冲区分析 多种类型要素栅格化 总结 参考链接 一.前言 上两篇文章介绍了如何使用Geotrellis进行矢量数据栅格化以及栅格渲染,本文主要介绍栅格化过程中常用到的缓冲区分 ...
- ios如何在#import方面提升编译性能
模块的使用非常简单,对于存在的工程,第一件事情就是让这个功能生效.可以在项目的Build Settings 中搜索Modules 找到这个选项,做以下的设置 默认的情况下都是开启的 对于系统自带的只需 ...
- 设置SharePoint Server 2013 的匿名访问
默认情况下,SharePoint Server 2013 是关闭匿名访问的,但是某些环境下我们又需要将这个匿名访问对全员开放,怎么操作更加安全与便捷,对于一个崭新的环境我们可以这样操作. 首先在当前需 ...
- Oracle库Delete删除千万以上普通堆表数据的方法
需求:Oracle数据库delete删除普通堆表千万条历史记录. 直接删除的影响: 1.可能由于undo表空间不足从而导致最终删除失败的问题: 2.可能导致undo表空间过度使用,影响到其他用户正常操 ...
- SQL Server SQL性能优化之--数据库在“简单”参数化模式下,自动参数化SQL带来的问题
数据库参数化的模式 数据库的参数化有两种方式,简单(simple)和强制(forced),默认的参数化默认是“简单”,简单模式下,如果每次发过来的SQL,除非完全一样,否则就重编译它(特殊情况会自动参 ...
- nodejs学习笔记一——nodejs安装
a.nodejs安装 nodejs的安装没有什么说的默认安装即可.安装包官网下载即可:nodejs官网 本人用的是window的安装包node-v4.2.6-x64.msi 安装完成后打开命令行查看使 ...