Android ListView setOnItemClickListener点击无效原因分析

转至:http://www.jb51.net/article/77792.htm

这篇文章主要介绍了Android 中ListView setOnItemClickListener点击无效原因分析的相关资料,需要的朋友可以参考下
id="iframeu2261530_0" src="http://pos.baidu.com/acom?sz=680x200&rdid=2261530&dc=2&di=u2261530&dri=0&dis=0&dai=2&ps=419x125&coa=at%3D3%26rsi0%3D680%26rsi1%3D200%26pat%3D6%26tn%3DbaiduCustNativeAD%26rss1%3D%2523FFFFFF%26conBW%3D1%26adp%3D1%26ptt%3D0%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D14%26rss2%3D%2523000000%26titSU%3D0%26ptbg%3D90%26piw%3D0%26pih%3D0%26ptp%3D0&dcb=BAIDU_UNION_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1454576401781&ti=Android%20%E4%B8%ADListView%20setOnItemClickListener%E7%82%B9%E5%87%BB%E6%97%A0%E6%95%88%E5%8E%9F%E5%9B%A0%E5%88%86%E6%9E%90_Android_%E8%84%9A%E6%9C%AC%E4%B9%8B&ari=1&dbv=0&drs=1&pcs=1240x716&pss=1240x425&cfv=20&cpl=6&chi=1&cce=true&cec=GBK&tlm=1453970178&ltu=http%3A%2F%2Fwww.jb51.net%2Farticle%2F77792.htm&ltr=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DZbT_IYdjoY_4h6oQLJXOJNpY5SQ0nZCcDoX4mr97U1x5LT3OFkSZ1WU3YhrSUKvU%26wd%3D%26eqid%3De395649100075cc90000000256b3124b&ecd=1&psr=1280x800&par=1276x777&pis=-1x-1&ccd=24&cja=true&cmi=29&col=zh-cn&cdo=-1&tcn=1454576402&qn=bef97fab5d7d095f&tt=1454576401758.48.257.258" width="680" height="200" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="display: block; border-width: 0px; vertical-align: bottom; margin: 0px;">

前言

最近在做项目的过程中,在使用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方法中调用,该方法用来处理点击事件,返回结果表示是否消耗当前事件。

当点击事件触发之后的流程


建瓯
建瓯
高仿手表
高仿手表
吉祥航空好不好
吉祥航空好不好
深圳it培训
深圳it培训
北大精品课程
北大精品课程

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@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);那么我们可以想到问题发生的原因应该就是在这个方法了里了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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以及相关代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@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方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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判断

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

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

View 和 ViewGroup 的 hasFocusable

ViewGroup的hasFocusable

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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

?
1
2
3
4
5
6
7
8
9
10
11
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也没有获取焦点,那么上面说到回调至性的条件判断也就的代码:

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

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

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

以上所述是本文给大家分享的Android 中ListView setOnItemClickListener点击无效原因分析,希望大家喜欢。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值