0x0
大家都知道在输入框长按文字,会出现编辑菜单。最近遇到一个需求:代码直接调出 EditText(TextView 需要设置 setTextIsSelectable(true))
的编辑菜单,这里我叫它 EditorActionMenu
。
既然通过长按可以调出,为何不直接 EditText.performLongClick()
或 View.showContextMenu()
方法。事实证明,此代码无法调出 EditorActionMenu
,下面进行分析如何弹出编辑菜单。
过程分析
- 编辑菜单弹出过程:
按下
->等待
->松开
->弹出菜单
对应 View
的 Touch
事件:
ACTION_DOWN
=> performLongClick
=> ACTION_UP
由于 TextView
拦截了 onTouchEvent
从onTouchEvent
和performLongClick
源码结合长按过程和代码调试可以分析出真正显示菜单的代码执行过程:
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 后执行
if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
mEditor.mDiscardNextActionUp = false;
if (mEditor.mIsInsertionActionModeStartPending) {
mEditor.startInsertionActionMode();
mEditor.mIsInsertionActionModeStartPending = false;
}
return superResult;
}
//.......
//.......
return superResult;
}
实现
反射实现
根据上面分析,菜单的弹出由 Editor
类控制,但这个类不对外开放 (被@hide
标注) 在开发中无法接触到这个类。利用反射可以实现,但考虑反射可能带来出乎意料的情况,并且 Android P
已禁止利用反射进行这种操作,这里就不考虑了。
模拟实现
利用代码模拟 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))
//延时发送松开事件
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
时间后才显示菜单。
优化模拟
不使用延时,直接调用 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()
//发送松开事件
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)
}
速度相比优化前稍微快些