大家都知道在輸入框長按文字,會出現(xiàn)編輯菜單。最近遇到一個需求:代碼直接調(diào)出 EditText(TextView 需要設(shè)置 setTextIsSelectable(true)) 的編輯菜單,這里我叫它 EditorActionMenu。

既然通過長按可以調(diào)出,為何不直接 EditText.performLongClick() 或 View.showContextMenu() 方法。事實證明,此代碼無法調(diào)出 EditorActionMenu,下面進行分析如何彈出編輯菜單。
過程分析
- 編輯菜單彈出過程:
按下->等待->松開->彈出菜單
對應 View 的 Touch 事件:
ACTION_DOWN => performLongClick => ACTION_UP
由于 TextView 攔截了 onTouchEvent 從onTouchEvent和performLongClick 源碼結(jié)合長按過程和代碼調(diào)試可以分析出真正顯示菜單的代碼執(zhí)行過程:
mEditor.onTouchEvent(event) => mEditor.performLongClick(handled); => mEditor.startInsertionActionMode(); =>
TextView onTouchEvent和performLongClick 源碼(省略部分代碼):
@Override
public boolean performLongClick() {
//.....
if (mEditor != null) {
//長按事件
handled |= mEditor.performLongClick(handled);
mEditor.mIsBeingLongClicked = false;
}
//....
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (mEditor != null) {
mEditor.onTouchEvent(event);
if (mEditor.mSelectionModifierCursorController != null
&& mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
return true;
}
}
final boolean superResult = super.onTouchEvent(event);
// ACTION_UP 后執(zhí)行
if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
mEditor.mDiscardNextActionUp = false;
if (mEditor.mIsInsertionActionModeStartPending) {
mEditor.startInsertionActionMode();
mEditor.mIsInsertionActionModeStartPending = false;
}
return superResult;
}
//.......
//.......
return superResult;
}
實現(xiàn)
反射實現(xiàn)
根據(jù)上面分析,菜單的彈出由 Editor 類控制,但這個類不對外開放 (被@hide標注) 在開發(fā)中無法接觸到這個類。利用反射可以實現(xiàn),但考慮反射可能帶來出乎意料的情況,并且 Android P 已禁止利用反射進行這種操作,這里就不考慮了。
模擬實現(xiàn)
利用代碼模擬 TouchEvent 來模擬手指動作。
這里使用 Kotlin 對 TextView 擴展一個 showEditorActionMenu 方法:
fun TextView.showEditorActionMenu() {
//獲取焦點
requestFocus()
//按下坐標
val x: Float = (width / 2).toFloat()
val y: Float = (height / 2).toFloat()
// 按下事件
onTouchEvent(newMotionEvent(MotionEvent.ACTION_DOWN, x, y))
//延時發(fā)送松開事件
postDelayed({
onTouchEvent(newMotionEvent(MotionEvent.ACTION_UP, x, y))
}, ViewConfiguration.getLongPressTimeout().toLong())
}
private fun newMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
//無需考慮 按下時間和事件時間
return MotionEvent.obtain(0, 0, action, x, y, 0)
}
不過上面有個缺點, postDelayed 造成等待 LongPressTimeout 時間后才顯示菜單。

優(yōu)化模擬
不使用延時,直接調(diào)用 ACTION_DOWN => performLongClick => ACTION_UP
fun TextView.showEditorActionMenu() {
//獲取焦點
requestFocus()
//按下坐標
val x: Float = (width / 2).toFloat()
val y: Float = (height / 2).toFloat()
// 按下事件
onTouchEvent(newMotionEvent(MotionEvent.ACTION_DOWN, x, y))
//長按事件
performLongClick()
//發(fā)送松開事件
onTouchEvent(newMotionEvent(MotionEvent.ACTION_UP, x, y))
}
private fun newMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
//無需考慮 按下時間和事件時間
return MotionEvent.obtain(0, 0, action, x, y, 0)
}
速度相比優(yōu)化前稍微快些
