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

Posted

技术标签:

【中文标题】EditText:禁用文本选择处理程序单击事件上的粘贴/替换菜单弹出【英文标题】:EditText: Disable Paste/Replace menu pop-up on Text Selection Handler click event 【发布时间】:2015-03-08 07:55:21 【问题描述】:

我的目标是拥有一个没有花哨功能的EditText,只是用于更轻松地移动光标的文本选择处理程序——因此没有上下文菜单或弹出窗口。

根据this solution,我通过使用 ActionMode 回调事件禁用了文本编辑功能操作栏的外观(复制/粘贴等)。

当字段中存在文本并且在文本中发生单击时,中间的中间文本选择句柄(见下图)仍会出现。伟大的!我想保持这种行为。我不希望在单击文本选择句柄时出现“粘贴”菜单。

我还通过在样式 XML 中设置 android:longClickable="false" 禁用了 EditText 的长按输入。禁用长按可防止在单击并按住鼠标(即长按)时出现“粘贴/替换”菜单,但是当在文本内单击(单次触摸)鼠标时,将出现文本选择句柄,并且当单击文本选择句柄本身,然后出现“粘贴”菜单选项(当剪贴板中有文本时)。这是我试图阻止的。

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

没有禁用剪贴板服务或编辑 Android 源代码,有什么方法可以阻止它显示吗?我尝试为android:textSelectHandleWindowStyle 定义一个新样式,并将android:visibility 设置为gone,但它不起作用(应用程序冻结了一段时间,否则它会显示)。

【问题讨论】:

@MarcinOrlowski 有商业原因——我不能在这里讨论——为什么需要这样做。这不适用于“应用商店”上的应用。 ***.com/questions/6275299/… @BhavinChauhan - 感谢您的链接,但所有答案都有各种禁用长按、禁用操作工具栏的复制/粘贴(我已经完成)或更改文本剪贴板。 您好,除了自定义编辑文本之外,找到任何解决方案了吗?同样的场景,我想动态使用它。有什么建议吗? 【参考方案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 的剪切、复制、粘贴选项。

【讨论】:

-1 感谢您的回复。我在问题中提到“我通过使用 ActionMode Callback 事件禁用了文本编辑功能操作栏的外观(复制/粘贴等)”。此响应是该问题 (***.com/a/22756538/3063884) 中答案的复制/粘贴,它通过拦截将导致它被创建的回调来防止操作栏(带有剪切、复制、粘贴等的顶部水平栏)出现, 并返回 false。但是,这并不能解决我的问题 - 单击文本选择句柄时会出现粘贴菜单。【参考方案2】:

或者干脆使用

yourEditText.setLongClickable(false);

XML 中的或

android:longClickable="false"

更新

实际上用户想要禁用文本选择句柄本身

1.创建一个形状(handle.xml)

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

 <size
    android:
    android: />
 </shape>

2。在你的 EditText

 android:textSelectHandle="@drawable/handle"

【讨论】:

这并不能解决我的问题。 (我已经实现了这个,但我可能应该在问题中提到)。禁用长按可防止在单击并按住鼠标(即长按)时出现“粘贴/替换”菜单。我正在描述的场景是在文本中单击(单击)鼠标时。发生这种情况时,会出现文本选择句柄,当单击文本选择句柄本身时,会出现“粘贴”菜单选项。这就是我要防止的。 (请注意,这也是我在帖子中引用的问题的答案 - ***.com/a/13821250/3063884 和 ***.com/a/26029022/3063884) 通常答案与最接近的情况有关。让我再看看你的问题。 几件事(我注意到你的编辑):我不想禁用文本选择句柄(来自问题:“我想保持这种行为。”)——我想要那个允许轻松重新定位光标。这是我不想要的“粘贴”上下文菜单。更新您的帖子会使光标句柄消失(除非我弄错了)。此外,为了重现我的场景(听起来可能很明显),但要显示“粘贴”菜单,剪贴板中需要有一些文本。【参考方案3】:

解决方案:在EditText 中覆盖isSuggestionsEnabledcanPaste

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

有关坚韧不拔的细节,请继续阅读。

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

isSuggestionsEnabled() 是 public,因此可能会被覆盖。 canPaste() 不是,因此必须在派生类中被introducing a function of the same name 隐藏。

因此,将这些更新合并到一个还具有setCustomSelectionActionModeCallback 和disabled 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 中对此进行了测试。

【讨论】:

很遗憾,您的解决方案不起作用,因为即使您在自定义 EditText 中创建了 boolean canPaste() 方法,也会从 Editor 类中调用原始基本方法。所以私有方法不能被这样调用false的方式重写。 @VladimirRyhlitskiy 您是否真的尝试过代码,或者这是假设(或您的编译器中的缺陷)?上面发布的代码对我来说是有效的并且运行良好。在我的堆栈跟踪中,来自Editor$ActionPopupWindow.show()(它调用boolean canPaste = mTextView.canPaste();)的调用调用NoMenuEditText.canPaste(),这是有道理的,因为本地上下文(SimpleEditText)中的canPaste()将首先被调用。也许你应该尝试一下。 它不适用于我的 Nexus 5 和 Android 5.1。马上试了一下。我有剪贴板中的值,我在调试器中看到以下内容:c2n.me/3inoPZG。所以 mTextView 具有正确的类型 - 我的自定义编辑文本,其中定义了返回 false 值的 canPaste() 方法。无论如何,价值是真实的。另外,我的视图的 canPaste() 方法中有断点,并且代码没有转到它。 @VladimirRyhlitskiy 我在 v4.4.2 上对此进行了测试。底层代码可能在 v4.4.2 和 v5.1 之间发生了变化。 为什么 android 不给我们禁用复制粘贴的选项,一切都需要在 android 中进行破解【参考方案4】:

当蓝色视图(插入控制器)根本没有出现时,找到了另一种解决方案。我使用反射来设置编辑器类的目标布尔字段。查看 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 上测试

【讨论】:

无法在我的nexus 6 上使用 5.1.1 android,mInsertionControllerEnabled 设置为 false,仍然显示粘贴提示。 (我想用可复制的只读文本制作一个edittext,但总是弹出烦人的粘贴提示)【参考方案5】:
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:
    android: />
 </shape>


By Using these two combinations my problem is solved.

【讨论】:

-1 这几乎是@HardikChauhan ***.com/a/27965349/3063884 上述答案的精确副本 - 另请参阅我在该答案之后的评论,了解为什么它不能解决问题(粘贴菜单出现在单击文本选择句柄)。【参考方案6】:

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

创建自定义 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);

【讨论】:

【参考方案7】:

以上解决方案都不适合我。我已经设法完成了我的解决方案(之后进行解释),它禁止在 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;

【讨论】:

此解决方案适用于什么版本的 Android SDK?这可能会阻止粘贴操作,但不会阻止“粘贴”菜单的出现,这是最初的问题是 [onTextContextMenuItem(int id) - 当文本视图的上下文菜单选项是选择。] 哦,我明白了,这个解决方案适用于我的应用程序(来自 API 15+)。但我无法让粘贴出现,我已经尝试了所有解决方案。我的用户不能长按,不能选择单词和任何一个,但是如果他点击光标,粘贴菜单总是出现:(所以我的想法是,如果这会出现,至少我会让他做所有其他常见的操作,只是阻止粘贴发生。从用户体验的角度来看这是错误的,但就我而言,这个功能确实需要 我理解你的沮丧!【参考方案8】:

这是一个禁用“粘贴”弹出窗口的技巧。你必须重写EditText 方法:

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

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

【讨论】:

谢谢,它适用于 Redmi 设备..with @libin 代码【参考方案9】:

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

//class 
ClipboardManager clipboard;

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

【讨论】:

请在整个代码中添加语法高亮,而不仅仅是一行。这提高了可读性。 这在完全受控的环境中可能是可以接受的,但通常用户会在剪贴板上放置一些内容,以便稍后粘贴——在不通知的情况下清除它会非常烦人。【参考方案10】:

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

    禁用复制/粘贴弹出窗口。 禁用文本选择处理程序。 光标仍显示在文本末尾。 仍在显示键盘。

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);

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

【讨论】:

当且仅当屏幕中只有 1 个 EditText 时它才会起作用。是的,不会显示菜单。【参考方案11】:

只需覆盖一种方法:

@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】:

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

Java:

ActionMode.Callback callback = new ActionMode.Callback() 
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) 
                return true;
            

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) 
                if (menu != null) 
                    menu.removeItem(android.R.id.paste);
                
                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 
        return true
    

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

    override fun onDestroyActionMode(mode: ActionMode?) 

然后在 EditText 中使用站点:

fun preventPaste() 
    customInsertionActionModeCallback = callback
    customSelectionActionModeCallback = callback

【讨论】:

最好不要删除onCreateActionMode中的项目,而是删除onPrepareActionMode中的项目,因为第一个仅在初始化期间运行,而后一个在菜单刷新时也会调用。见the docs【参考方案13】:

我找到了一个简单的解决方案。希望它对某人有所帮助,扩展 Edittetxt 类并覆盖以下方法。 同样,如果您想通过比较 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) 
    

【讨论】:

我们不能依赖标题的文字,因为它们会被翻译成各种语言。【参考方案14】:

通过下面提到的所有 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?) 

【讨论】:

【参考方案15】:

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

【讨论】:

理想情况下,这将包含为代码格式的代码,而不是无法复制/粘贴或搜索的图像... 您可以查看上面添加图片的链接 链接可能会过时,因此在 SO 上,最好复制和复制与答案相关的内容作为答案的一部分。

以上是关于EditText:禁用文本选择处理程序单击事件上的粘贴/替换菜单弹出的主要内容,如果未能解决你的问题,请参考以下文章

Android EditText单击触发onclick事件处理

从组件角度禁用事件单击

Android文本输入框EditText禁用分号

哪个是 MFC C++ 中单选组合框的事件处理程序

如何从编辑文本中删除键盘,但单击编辑文本时显示键盘?

JavaScript:通过双击禁用文本选择