EditText:在文本选择处理程序单击事件上禁用粘贴/替换菜单弹出窗口

时间:2015-01-09 22:03:49

标签: android android-edittext android-widget contextmenu

我的目标是拥有一个没有花哨功能的EditText,只需要文本选择处理程序就可以更轻松地移动光标 - 所以没有上下文菜单或弹出窗口。

我已按照this solution消耗了ActionMode Callback事件,禁用了文本编辑功能操作栏(复制/粘贴等)的外观。

当文本存在于字段中并且文本中出现单击时,仍然会出现中间中间文本选择句柄(请参见下图)。大!我想保持这种行为。我不想要的是" PASTE"单击文本选择句柄本身时显示的菜单。

Text selection handle with paste menu

我还通过在样式XML中设置android:longClickable="false"来禁用EditText的长按输入。禁用长按可防止"粘贴/替换"单击鼠标并保持(即长按)时出现的菜单,但是当在文本中单击鼠标(单次触摸)时,会出现文本选择手柄,当单击文本选择手柄本身时,则&# 34;粘贴"菜单选项出现(当剪贴板中有文本时)。这就是我试图阻止的。

从我从源头看到的内容,ActionPopupWindow是弹出PASTE / REPLACE选项的内容。 ActionPopupWindow是公共类android.widget.Editor中私有抽象类HandleView中的受保护变量(mActionPopupWindow)...

如果没有禁用剪贴板服务或编辑Android源代码,有没有办法阻止它显示?我尝试为android:textSelectHandleWindowStyle定义一种新样式,并将android:visibility设置为gone,但它没有效果(app会冻结一段时间,否则会显示)。

15 个答案:

答案 0 :(得分:20)

解决方案:覆盖isSuggestionsEnabled中的canPasteEditText

对于快速解决方案,请复制下面的类 - 此类会覆盖EditText类,并相应地阻止所有事件。

对于细节,请继续阅读。

解决方案在于防止PASTE / REPLACE菜单出现在(未记录的)android.widget.Editor类的show()方法中。在菜单出现之前,检查if (!canPaste && !canSuggest) return;。用作设置这些变量的基础的两个方法都在EditText类:

因此,将这些更新合并到一个同时具有setCustomSelectionActionModeCallbackdisabled long-click的类中,这里是完整的类,可以阻止所有编辑(但仍显示text selection handler)以进行控制光标:

package com.cjbs.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;


/**
 *  This is a thin veneer over EditText, with copy/paste/spell-check removed.
 */
public class NoMenuEditText extends EditText
{
    private final Context context;

    /** This is a replacement method for the base TextView class' method of the same name. This 
     * method is used in hidden class android.widget.Editor to determine whether the PASTE/REPLACE popup
     * appears when triggered from the text insertion handle. Returning false forces this window
     * to never appear.
     * @return false
     */
    boolean canPaste()
    {
       return false;
    }

    /** This is a replacement method for the base TextView class' method of the same name. This method
     * is used in hidden class android.widget.Editor to determine whether the PASTE/REPLACE popup
     * appears when triggered from the text insertion handle. Returning false forces this window
     * to never appear.
     * @return false
     */
    @Override
    public boolean isSuggestionsEnabled()
    {
        return false;
    }

    public NoMenuEditText(Context context)
    {
        super(context);
        this.context = context;
        init();
    }

    public NoMenuEditText(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        this.context = context;
        init();
    }

    public NoMenuEditText(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init()
    {
        this.setCustomSelectionActionModeCallback(new ActionModeCallbackInterceptor());
        this.setLongClickable(false);
    }


    /**
     * Prevents the action bar (top horizontal bar with cut, copy, paste, etc.) from appearing
     * by intercepting the callback that would cause it to be created, and returning false.
     */
    private class ActionModeCallbackInterceptor implements ActionMode.Callback
    {
        private final String TAG = NoMenuEditText.class.getSimpleName();

        public boolean onCreateActionMode(ActionMode mode, Menu menu) { return false; }
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; }
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; }
        public void onDestroyActionMode(ActionMode mode) {}
    }
} 

我在Android v4.4.2和v4.4.3中测试了这个。

答案 1 :(得分:14)

或只是使用

yourEditText.setLongClickable(false);

XML in XML

android:longClickable="false"

<强>更新

实际上,用户希望禁用文本选择句柄本身

<强> 1。创建一个形状(handle.xml)

 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >

 <size
    android:height="0dp"
    android:width="0dp" />
 </shape>

<强> 2。在您的EditText

 android:textSelectHandle="@drawable/handle"

答案 2 :(得分:6)

这是一个禁用&#34;粘贴&#34;弹出。您必须覆盖EditText方法:

@Override
public int getSelectionStart() {
    for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
        if (element.getMethodName().equals("canPaste")) {
            return -1;
        }
    }
    return super.getSelectionStart();
}

此解决方案也适用于较新版本的Android,与接受的答案不同。

答案 3 :(得分:5)

当蓝色视图(插入控制器)根本没有出现时找到另一个解决方案。我使用反射来设置Editor类的目标布尔字段。查看android.widget.Editor和android.widget.TextView以获取更多详细信息。

将以下代码添加到自定义EditText中(包含本主题中的所有先前代码):

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        // setInsertionDisabled when user touches the view
        this.setInsertionDisabled();
    }
    return super.onTouchEvent(event);
}

/**
 * This method sets TextView#Editor#mInsertionControllerEnabled field to false
 * to return false from the Editor#hasInsertionController() method to PREVENT showing
 * of the insertionController from EditText
 * The Editor#hasInsertionController() method is called in  Editor#onTouchUpEvent(MotionEvent event) method.
 */

private void setInsertionDisabled() {
    try {
        Field editorField = TextView.class.getDeclaredField("mEditor");
        editorField.setAccessible(true);
        Object editorObject = editorField.get(this);

        Class editorClass = Class.forName("android.widget.Editor");
        Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
        mInsertionControllerEnabledField.setAccessible(true);
        mInsertionControllerEnabledField.set(editorObject, false);
    }
    catch (Exception ignored) {
        // ignore exception here
    }
}

另外,也许你可以找到比onTouch()更好的地方来调用目标方法。

在Android 5.1上测试

答案 4 :(得分:5)

我找不到隐藏菜单弹出窗口的方法,但如果用户点击菜单,您可以禁用粘贴

创建自定义EditText并覆盖onTextContextMenuItem方法,并为android.R.id.pasteandroid.R.id.pasteAsPlainText菜单ID返回false。

@Override
public boolean onTextContextMenuItem(int id) {
    switch (id){
        case android.R.id.paste:
        case android.R.id.pasteAsPlainText:
            return false;

    }
    return super.onTextContextMenuItem(id);
}

答案 5 :(得分:2)

您可以通过执行以下操作完全删除menuItem:

Java:

ActionMode.Callback callback = new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                if (menu != null) {
                    menu.removeItem(android.R.id.paste);
                }
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                return false;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {

            }
        };

        mEditText.setCustomInsertionActionModeCallback(callback);

        mEditText.setCustomSelectionActionModeCallback(callback);

科特琳:

val callback = object : ActionMode.Callback {
    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
        return false
    }

    override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
        menu?.removeItem(android.R.id.paste)
        return true
    }

    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
        return true
    }

    override fun onDestroyActionMode(mode: ActionMode?) {}
}

然后在EditText中使用站点:

fun preventPaste() {
    customInsertionActionModeCallback = callback
    customSelectionActionModeCallback = callback
}

答案 6 :(得分:1)

Use this in java file

if (android.os.Build.VERSION.SDK_INT < 11) {
    editText.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {

        @Override`enter code here`
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenuInfo menuInfo) {
            // TODO Auto-generated method stub
            menu.clear();
        }
    });
} else {
    editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub

        }

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public boolean onActionItemClicked(ActionMode mode,
                MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }`enter code here`
    });
}


With this code also add android:textSelectHandle="@drawable/handle"
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >

 <size
    android:height="0dp"
    android:width="0dp" />
 </shape>


By Using these two combinations my problem is solved.

答案 7 :(得分:1)

如果您需要删除PASTE建议,请在长按之前清除剪贴板。

//class 
ClipboardManager clipboard;

//oncreate 
clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("","");
clipboard.setPrimaryClip(clip);

答案 8 :(得分:1)

通过以下所有3个更改修复了该问题

fun TextView.disableCopyPaste() {
isLongClickable = false.  //  change 1 ,  disable Long click
setTextIsSelectable(false). //  change 2  ,  disable text selection click

//change 3 ,  return false from all actionmode 
 customSelectionActionModeCallback = object : ActionMode.Callback {
    override fun onCreateActionMode(mode: ActionMode?, menu: Menu): Boolean {
        return false
    }

    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu): Boolean {
        return false
    }

    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem): Boolean {
        return false
    }

    override fun onDestroyActionMode(mode: ActionMode?) {}
}

}

答案 9 :(得分:0)

以上解决方案均不适合我。我已经设法做了我的解决方案(后面的解释),它禁止在EditText上粘贴任何东西,同时保持所有其他操作有效。
主要是,您必须在EditText的实现上覆盖此方法:

@Override
public boolean onTextContextMenuItem (int id) {
    if (id == android.R.id.paste) return false;

    return super.onTextContextMenuItem(id);
}

因此,调查EditText代码后,在所有检查后,粘贴(以及EditText上的所有ContextMenu操作)都发生在名为onTextContextMenuItem的方法中:

public boolean onTextContextMenuItem(int id) {
    int min = 0;
    int max = mText.length();

    if (isFocused()) {
        final int selStart = getSelectionStart();
        final int selEnd = getSelectionEnd();

        min = Math.max(0, Math.min(selStart, selEnd));
        max = Math.max(0, Math.max(selStart, selEnd));
    }

    switch (id) {
        case ID_SELECT_ALL:
            // This does not enter text selection mode. Text is highlighted, so that it can be
            // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
            selectAllText();
            return true;

        case ID_PASTE:
            paste(min, max);
            return true;

        case ID_CUT:
            setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
            deleteText_internal(min, max);
            stopSelectionActionMode();
            return true;

        case ID_COPY:
            setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
            stopSelectionActionMode();
            return true;
    }
    return false;
}

如果您注意到,粘贴只会在id == ID_PASTE时发生,因此,请再次查看EditText代码:

static final int ID_PASTE = android.R.id.paste;

答案 10 :(得分:0)

我找到了一种简单而可靠的方法。想法是消耗触摸事件,以防止触摸事件达到下划线默认代码。

  1. 禁用复制/粘贴弹出窗口。
  2. 禁用文本选择处理程序。
  3. 仍然在文本末尾显示光标。
  4. 仍然显示键盘。
  5. maskedEditText.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            focusAndShowKeyboard(view.getContext(), maskedEditText);
            // Consume the event.
            return true;
        }
    });
    
    private static void focusAndShowKeyboard(Context context, EditText editText) {
        editText.requestFocus();
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
    }
    

    enter image description here

    注意,闪烁的光标仍显示在文本末尾。只是截图无法捕捉它。

答案 11 :(得分:0)

只需覆盖一个方法:

@Override
protected MovementMethod getDefaultMovementMethod() {
    // we don't need arrow key, return null will also disable the copy/paste/cut pop-up menu.
    return null;
}

答案 12 :(得分:0)

我找到了一个简单的解决方案,希望可以对下面的方法扩展Edittetxt类和ovverride有所帮助。 如果您想通过比较menu.getItem(i).getTitle()来禁用其他选项,可以采用相同的方法。

private class ActionModeCallbackInterceptor implements ActionMode.Callback
    {
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            return true;
        }

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            for(int i =0;i<menu.size();i++){
                if(menu.getItem(i).getTitle().toString().equals("Clipboard") 
               || menu.getItem(i).getTitle().toString().equals("Paste")) {
                    menu.getItem(i).setVisible(false);
                }
            }
              return false;
        }

        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return false;
        }
        public void onDestroyActionMode(ActionMode mode) {}
    }

答案 13 :(得分:-1)

您可以使用此代码:

if (android.os.Build.VERSION.SDK_INT < 11) {
    editText.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenuInfo menuInfo) {
            // TODO Auto-generated method stub
            menu.clear();
        }
    });
} else {
    editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub

        }

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public boolean onActionItemClicked(ActionMode mode,
                MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }
    });
}

onCreateActionMode返回false会禁用API级别大于11的剪切,复制,粘贴选项。

答案 14 :(得分:-1)

我刚刚在 Android 11 中使用了上述一些解决方案,并且运行良好,您可以使用以下要点。 https://gist.github.com/harshmittal2810/26429eb426dd1b31750cb33b47f449a6 enter image description here