爆裂吧现实

不想死就早点睡

0%

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

问题件简述

RecycleView 中有 EditText 的时候,上下滚动被复用的时候,发现长按 EditText 没有弹出上下文菜单

分析

调试了一波后发现,正常情况下长按的时候 TextViewperformClick() 函数返回的是 true,没有弹出上下文的时候,此函数返回了 fasle

首先找 TextView 的长按事件 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
@Override
public boolean performLongClick() {
boolean handled = false;

if (mEditor != null) {
mEditor.mIsBeingLongClicked = true;
}

if (super.performLongClick()) {
handled = true;
}

if (mEditor != null) {
handled |= mEditor.performLongClick(handled);
mEditor.mIsBeingLongClicked = false;
}

if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (mEditor != null) mEditor.mDiscardNextActionUp = true;
}

return handled;
}

先正常调试

  • super.performLongClick() 返回 false
  • mEditor.performLongCLick() 返回 true

无法弹出菜单的时候调试

  • super.performLongClick() 返回 false
  • mEdit.performLongClick() 返回 false

那么问题出现在 mEdit.performLongClick() 上面,查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean performLongClick(boolean handled) {
// Long press in empty space moves cursor and starts the insertion action mode.
if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
mInsertionControllerEnabled) {
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);
Selection.setSelection((Spannable) mTextView.getText(), offset);
getInsertionController().show();
mIsInsertionActionModeStartPending = true;
handled = true;
}
// 此处省略一些代码...
return handled;
}

正常调试

  • mInsertionControllerEnabledtrue

无法弹出菜单时候的调试

  • mInsertionControllerEnablefalse

那么问题定位到 mInsertionControllerEnabled 的布尔值了,寻找这个成员属性被修改的地方,发现在 Editor#prepareCursorControllers 内,查看源码

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
void prepareCursorControllers() {
boolean windowSupportsHandles = false;

ViewGroup.LayoutParams params = mTextView.getRootView().getLayoutParams();
if (params instanceof WindowManager.LayoutParams) {
WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
|| windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
}

boolean enabled = windowSupportsHandles && mTextView.getLayout() != null;
mInsertionControllerEnabled = enabled && isCursorVisible();
mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected();

if (!mInsertionControllerEnabled) {
hideInsertionPointCursorController();
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.onDetached();
mInsertionPointCursorController = null;
}
}

if (!mSelectionControllerEnabled) {
stopTextActionMode();
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.onDetached();
mSelectionModifierCursorController = null;
}
}
}

到这里不用调试就可以看出问题出在哪里 ViewGroup.LayoutParams params = mTextView.getRootView().getLayoutParams(); 这句话,如果在 ViewHolder 绑定数据的时候被执行的话,永远不可能是 WindowManager.LayoutParams,因为此时 EdtiText 根本没有被 attachToWindow

解决方案

既然知道了是 EditText 没有被 attachToWindow 的时候调用了 Editor#prepareCursorControllers 函数导致的,那么我在 attachToWindow 之后重新调用一遍这个函数不就可以了?

因为这个是包权限的方法,所以需要找间接调用的地方,找到了 setCursorVisible 方法中有被调用,所以只要在 onAttachToWindow 的时候先设置成 false 然后再设置成 true 就可以了

1
2
3
4
5
6
7
8
9
10
11
edtImgDesc.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
edtImgDesc.setCursorVisible(false);
edtImgDesc.setCursorVisible(true);
}

@Override
public void onViewDetachedFromWindow(View v) {
}
});