https://blog.csdn.net/weixin_30784945/article/details/98094246
前言
最近在做项目的过程中,在使用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;
-
}
- 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
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
代码比较长,我们主要看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;
-
}
- 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
这里主要看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));
-
}
-
}
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
第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;
-
}
- 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
看第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;
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
好了,搞了半天,终于到点上了。第3
行代码很明显了,就是如果有ItemClickListener,就执行他的onItemClick方法,最终回调到我们常见的那个方法。
到这里,相信大家已经知道,关键代码就是刚才上面我们分析的那一个if判断
-
if (inList
&&
!child
.hasFocusable()) {
-
if (mPerformClick
==
null) {
-
mPerformClick
=
new PerformClick();
-
}
-
...
..
-
}
- 1
- 2
- 3
- 4
- 5
- 6
也就是只有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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
看源码我们可以知道:
- 如果 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();
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 在触摸模式下如果不可获取焦点,先遍历 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();
-
}
-
...
..
-
}
- 1
- 2
- 3
- 4
- 5
- 6
if条件成立,所有执行了回调。
第二种情况,item,设置了descendantFocusability=”blocksDescendants”,所有没有遍历子 View,child.hasFocusable()直接返回false了。
好了,分析到这里相信大家已经很明白了。