Android Edittext 长按没有弹出上下文菜单

问题分析

Edittext 在 ListView 的 item 中,没有其他的类似焦点冲突问题,在没有头绪的情况下,先从源码开始分析问题根源,Edittext 关于处理长按事件的部分是继承 TextView 的,所以先看 TextView 的 performLongClick 方法

TextView 的 performLongClick 方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean performLongClick() {
boolean handled = false;
if (super.performLongClick()) {
handled = true;
}
if (mEditor != null) {
handled |= mEditor.performLongClick(handled);
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (mEditor != null) mEditor.mDiscardNextActionUp = true;
}
return handled;
}

可以发现 TextView 的 performLongClick 方法主要分为两步:一、调用 View 的 performLongClick;二、调用 Editor 的 performLongClick

Editor 的 performLongClick 方法源码:

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
public boolean performLongClick(boolean handled) {
// Long press in empty space moves cursor and shows the Paste affordance if available.
if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
mInsertionControllerEnabled) {
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);
stopSelectionActionMode();
Selection.setSelection((Spannable) mTextView.getText(), offset);
getInsertionController().showWithActionPopup();
handled = true;
}
if (!handled && mSelectionActionMode != null) {
if (touchPositionIsInSelection()) {
// Start a drag
final int start = mTextView.getSelectionStart();
final int end = mTextView.getSelectionEnd();
CharSequence selectedText = mTextView.getTransformedText(start, end);
ClipData data = ClipData.newPlainText(null, selectedText);
DragLocalState localState = new DragLocalState(mTextView, start, end);
mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
getSelectionController().hide();
selectCurrentWord();
getSelectionController().show();
}
handled = true;
}
// Start a new selection
if (!handled) {
handled = startSelectionActionMode();
}
return handled;
}

分析 Editor 的 performLongClick 方法,可以发现如果传入的参数 handled 为 true,那么 performLongClick 相当于什么都没做。而从代码中可以看出 Editor 的这个方法就是用来显示复制、粘贴的上下文菜单的。
所以在 TextView 的 performLongClick 中,调用 View 的 performLongClick 返回为 true 的话,调用 Editor 的 performLongClick 就什么都没做,也就不会显示复制、粘贴的上下文菜单了。

断点调试发现果然 super.performLongClick() 返回为 true,所以接下来就要分析为什么会返回 true。

View 的 performLongClick 方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
* OnLongClickListener did not consume the event.
*
* @return True if one of the above receivers consumed the event, false otherwise.
*/
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}

View 的 performLongClick 方法就是先调用 onLongClickListener,如果返回为 false,则调用 showContextMenu 方法,因为 Edittext 没有设置 onLongClickListener,所以肯定会调用 showContextMenu 方法

继续断点调试,下面是主要调试信息:

at android.widget.AbsListView.showContextMenuForChild(AbsListView.java:3006)
at android.view.ViewGroup.showContextMenuForChild(ViewGroup.java:657)
at android.view.View.showContextMenu(View.java:4507)
at android.view.View.performLongClick(View.java:4468)
at android.widget.TextView.performLongClick(TextView.java:8354)
at android.view.View$CheckForLongPress.run(View.java:18401)

继续看 AbsListView 的 showContextMenuForChild 方法源码:

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 showContextMenuForChild(View originalView) {
final int longPressPosition = getPositionForView(originalView);
if (longPressPosition >= 0) {
final long longPressId = mAdapter.getItemId(longPressPosition);
boolean handled = false;
if (mOnItemLongClickListener != null) {
handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
longPressPosition, longPressId);
}
if (!handled) {
mContextMenuInfo = createContextMenuInfo(
getChildAt(longPressPosition - mFirstPosition),
longPressPosition, longPressId);
handled = super.showContextMenuForChild(originalView);
}
return handled;
}
return false;
}

如果 onItemLongClickListener 不为空的话,会先调用它,如果返回 false 再继续调用 parent 的 showContextMenuForChild 方法。
再查看我的代码,发现 ListView 有设置 onItemLongClickListener,而且 onItemLongClick 方法最后一定会 return true。

  • 原因分析

通过以上的工作可以知道,原因就是因为 ListView 的 onItemLongClickListener 的 onItemLongClick 方法返回 true,最终导致调用 View 的 performLongClick 返回为 true 的,调用 Editor 的 performLongClick 就什么都没做,也就不会显示复制、粘贴的上下文菜单了。

  • 解决方法

在 onItemLongClick 方法进行判断有没有是不是长按 Edittext太麻烦了,我选择的方法时:重载 Edittext 的 showContextMenu 方法,默认返回为 false
经过检验,这个方法是可行的。

小结

如果本来就是需要 Edittext 长按不弹出上下文菜单,有下面几种方法:

  1. Edittext 设置 onLongClickListener,返回 true

  2. 重载 Edittext 的 showContextMenu 方法,默认返回为 true