【问题标题】:EditText: Disable Paste/Replace menu pop-up on Text Selection Handler click eventEditText:禁用文本选择处理程序单击事件上的粘贴/替换菜单弹出
【发布时间】: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 有商业原因——我不能在这里讨论——为什么需要这样做。这不适用于“应用商店”上的应用。
  • @BhavinChauhan - 感谢您的链接,但所有答案都有各种禁用长按、禁用操作工具栏的复制/粘贴(我已经完成)或更改文本剪贴板。
  • 您好,除了自定义编辑文本之外,找到任何解决方案了吗?同样的场景,我想动态使用它。有什么建议吗?

标签: android android-edittext android-widget contextmenu


【解决方案1】:

解决方案:在EditText 中覆盖isSuggestionsEnabledcanPaste

对于快速解决方案,请复制下面的类 - 该类覆盖 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 中对此进行了测试。

【讨论】:

  • 不幸的是,您的解决方案不起作用,因为即使您在自定义 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 中的 hack
【解决方案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:height="0dp"
    android:width="0dp" />
 </shape>

2。在你的 EditText

 android:textSelectHandle="@drawable/handle"

【讨论】:

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

这是一个禁用“粘贴”弹出窗口的技巧。你必须重写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 代码
【解决方案4】:

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

创建自定义 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】:

    当蓝色视图(插入控制器)根本没有出现时,找到了另一种解决方案。我使用反射来设置编辑器类的目标布尔字段。查看 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,但总是弹出烦人的粘贴提示)
    【解决方案6】:

    您可以通过执行以下操作完全删除 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
    【解决方案7】:
    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.
    

    【讨论】:

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

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

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

    【讨论】:

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

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

    }

    【讨论】:

      【解决方案10】:

      以上解决方案都不适合我。我已经设法完成了我的解决方案(之后进行解释),它禁止在 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+)。但我无法让粘贴出现,我已经尝试了所有解决方案。我的用户不能长按,不能选择单词和任何一个,但是如果他点击光标,粘贴菜单总是出现:(所以我的想法是,如果这会出现,至少我会让他做所有其他常见的操作,只是阻止粘贴发生。从用户体验的角度来看这是错误的,但就我而言,这个功能确实需要
      • 我理解你的沮丧!
      【解决方案11】:

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

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

      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 时它才会起作用。是的,不会显示菜单。
      【解决方案12】:

      我找到了一个简单的解决方案。希望它对某人有所帮助,扩展 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) {}
          }
      

      【讨论】:

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

      您可以使用此代码:

      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 事件禁用了文本编辑功能操作栏的外观(复制/粘贴等)”。此响应是该问题 (stackoverflow.com/a/22756538/3063884) 中答案的复制/粘贴,它通过拦截将导致它被创建的回调来防止操作栏(带有剪切、复制、粘贴等的顶部水平栏)出现, 并返回 false。但是,这并不能解决我的问题 - 单击文本选择句柄时会出现粘贴菜单。
      【解决方案14】:

      只需覆盖一种方法:

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

      【讨论】:

        【解决方案15】:

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

        【讨论】:

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