大家都知道在輸入框長按文字芦倒,會出現(xiàn)編輯菜單畜疾。最近遇到一個需求:代碼直接調(diào)出 EditText(TextView 需要設(shè)置 setTextIsSelectable(true))
的編輯菜單,這里我叫它 EditorActionMenu
挖帘。
既然通過長按可以調(diào)出瞒窒,為何不直接 EditText.performLongClick()
或 View.showContextMenu()
方法捺僻。事實證明,此代碼無法調(diào)出 EditorActionMenu
崇裁,下面進行分析如何彈出編輯菜單匕坯。
過程分析
- 編輯菜單彈出過程:
按下
->等待
->松開
->彈出菜單
對應(yīng) 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)化前稍微快些