使用代碼調(diào)出 TextView/EditText 的編輯菜單

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

image

既然通過長按可以調(diào)出,為何不直接 EditText.performLongClick()View.showContextMenu() 方法。事實證明,此代碼無法調(diào)出 EditorActionMenu,下面進行分析如何彈出編輯菜單。

過程分析

  • 編輯菜單彈出過程: 按下 -> 等待 -> 松開 -> 彈出菜單

對應 ViewTouch 事件:

ACTION_DOWN => performLongClick => ACTION_UP

由于 TextView 攔截了 onTouchEventonTouchEventperformLongClick 源碼結(jié)合長按過程和代碼調(diào)試可以分析出真正顯示菜單的代碼執(zhí)行過程:

mEditor.onTouchEvent(event) => mEditor.performLongClick(handled); => mEditor.startInsertionActionMode(); =>

TextView onTouchEventperformLongClick 源碼(省略部分代碼):

    @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 來模擬手指動作。

這里使用 KotlinTextView 擴展一個 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 時間后才顯示菜單。

image

優(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)化前稍微快些

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容