禁用EditText上下文菜单

35
我正在为传统蒙古文制作一个垂直的EditText。通过将略微修改过的EditText嵌入旋转的ViewGroup中,我已经成功地实现了它。我需要创建完全自定义的上下文菜单,因为系统菜单不支持垂直文本,并且在ViewGroup旋转时也不会旋转。因此,我想彻底禁用系统上下文菜单。
请注意,这与仅尝试禁用复制/粘贴等的问题不同。

虽然我没有在模拟器中看到上下文菜单,但我在我的Android 5.0.2小米手机上看到了它。

我已经尝试过:

我愿意尝试一些hack方法,但我需要它在各种设备上都能正常工作。Mark Murphy (a Commons Guy) 曾经写道回复另一个尝试做类似事情的用户:

我怀疑即使你找到了答案,它也不会在所有设备上都起作用。设备制造商往往会为EditText自己定制“上下文菜单”,从而破坏开发者添加菜单项的尝试。我猜想,尝试阻止那个上下文菜单将会有类似的结果。

那我是不是没有办法了?

我现在能想到的唯一办法就是彻底重写TextViewEditText(通过修改Android源码)。我知道有人做过类似的事情,但他的代码不是开源的。在采取这个重要步骤之前,我想在Stack Overflow上尝试寻求更简单的解决方案。
更新:我已经试图修改TextView源代码两天了,看起来像是一个6个月的项目。它是一堆相互关联的类。我需要另一个解决方案,但我已经没有任何想法了。
MVCE
这是我能想到的最简单的重新创建问题的方法。与我的自定义EditText无关。布局有一个由默认项目Hello World的TextView替换而成的单个EditText。我将min API更改为11以避免处理弃用的方法。
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText editText = (EditText) findViewById(R.id.edit_text);
        editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { return false; }
            @Override
            public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { return false; }
            @Override
            public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { return false; }
            @Override
            public void onDestroyActionMode(ActionMode actionMode) { }
        });
    }
}

模拟器中的上下文菜单(运行API 24)仍然会在我点击光标手柄时显示(但不是长按或双击)。这是一张图片:

enter image description here

在我的小米MIUI手机上运行Android 5.0,我可以在所有情况下(光标处理单击、长按、双击)获得上下文菜单。
更新
Aritra Roy的解决方案在模拟器中、他测试过的其他设备上以及我的设备上都有效。我已经接受了他的答案,因为它解决了我的原始问题。唯一的负面影响是文本选择也被禁用了。

虽然可能不必回答这个问题,但是我的自定义EditText代码在GitHub上 - Suragch
有没有一种方法可以禁用TextView上的上下文菜单? - Omer Faruk KAYA
谁是Aritra Roy,答案在哪里? - Mohd Qasim
1
@MohdQasim,显然那个答案被一个版主删除了,理由是它抄袭了https://dev59.com/3W015IYBdhLWcg3w_Q0_和https://dev59.com/nF4c5IYBdhLWcg3weqW9#30490027。 - Suragch
8个回答

6
这个解决方案非常简单。
public class MainActivity extends AppCompatActivity {

EditText et_0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    et_0 = findViewById(R.id.et_0);

    et_0.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            //to keep the text selection capability available ( selection cursor)
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            //to prevent the menu from appearing
            menu.clear();
            return false;
        }

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

        @Override
        public void onDestroyActionMode(ActionMode mode) {

        }
    });
   }
}

enter image description here


我还没有测试过,但看起来你的答案不同之处在于你调用了 menu.clear()。当你点击光标手柄、长按或双击时呢?菜单会出现吗? - Suragch
@Suragch 测试一下并让我知道。 - Muhammad Ali
3
工作正常。谢谢! - DmitryKanunnikoff
我尝试了这个解决方案,但是当我执行长按操作时,上下文菜单仍然出现。 - CodeAlo
不工作.... - Vitaly

5

我为EditText编写了这段代码,对于这样的问题它可以正常工作。

try {
    edtName.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            edtName.setSelection(0);
        }
    });
    edtName.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            return true;
        }
    });
    edtName.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { return false; }
        @Override
        public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { return false; }
        @Override
        public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { return false; }
        @Override
        public void onDestroyActionMode(ActionMode actionMode) { }
    });
} catch (Exception e) {
    e.printStackTrace();
}

1
我已经通过将您的解决方案替换为我的MVCE中的代码进行测试。在我的小米MIUI Android 5.0设备上,这仅禁用了长按的上下文菜单。双击和点击光标处理器时仍会显示菜单。我不指望您能为我的特定设备解决问题。我只是想让您知道它的存在。但是,在运行API 24的Android Studio模拟器中,当我单击光标处理器时仍会显示菜单。我在上面的答案中添加了一张图片来展示这种情况。 - Suragch

2
mEditText.setLongClickable(false);

这是禁用编辑文本的最简单方法。


2
这可能会禁用长按的上下文菜单,但不会禁用双击或单击光标的上下文菜单。 - Suragch
不工作..... - Vitaly

2
我最近也遇到了这个问题,我认为我有一个最好的解决方案,适用于我们想要启用文本选择和文本选择处理器,但完全删除上下文菜单的用例。
有两个上下文菜单:
1. customInsertionActionModeCallback - (粘贴,全选) 2. customSelectionActionModeCallback - (剪切,复制,分享等)
所有的解决方案都是针对删除第二个菜单,而不是OP所问的那个在OP点击光标控制拇指固定点时的插入回调。要删除两个回调,必须重写它们。
对于SelectionCallback(剪切,复制,粘贴)
customSelectionActionModeCallback = object : ActionMode.Callback {
    override fun onCreateActionMode(ActionMode mode, Menu menu) {
        menu.clear()
        return true;
    }

   override fun onPrepareActionMode(ActionMode mode, Menu menu) {
        menu.clear();
        return false;
    }

   override fun onActionItemClicked(ActionMode mode, MenuItem item) {
        return false;
    }

    override fun onDestroyActionMode(ActionMode mode) {

    }

同样地,对于InsertionCallback(selectAll,paste)也是如此。
    customInsertionActionModeCallback = object : ActionMode.Callback { 
    override fun onCreateActionMode(ActionMode mode, Menu menu) {
        menu.clear()
        return true;
    }

    override fun onPrepareActionMode(ActionMode mode, Menu menu) {
        menu.clear();
        return false;
    }

    override fun onActionItemClicked(ActionMode mode, MenuItem item) {
        return false;
    }

    override fun onDestroyActionMode(ActionMode mode) {

    }

1
太棒了!在stackoverflow上有很多关于这个问题的答案,但是你的是第一个提到customInsertionActionModeCallbackcustomSelectionActionModeCallback两个函数作用的。大多数人只提到后者,忽略了前者或者提供了对前者的解决方法。你的解决方案最简单也最正确。谢谢! - jho

1
这是如何阻止复制粘贴菜单以任何方式出现的方法。这个错误真的让我疯了,就像所有三星的错误一样,你知道它在他们的代码中,但你也知道他们不会很快修复它。无论如何,这里是奇妙的墙...

  1. 检查Android.Build.Model.toLowerCase().startsWith('sm-g930')。不要匹配整个字符串,最后一个字母是一个小版本标识符。我将这个布尔值存储在shouldBlockCopyPaste变量中,稍后会用到。

  2. 如果匹配,则要阻止显示复制粘贴菜单。这是你实际做到的!!!

覆盖这2个函数,你会注意到我的shouldBlockCopyPaste布尔值,这是为了让其他设备不被阻止。

   @Override
   public ActionMode StartActionMode (ActionMode.Callback callback){
      if (shouldBlockCopyPaste) {
        return null;
      } else {
        return super.StartActionMode(callback);
      }
    }

   @Override
   public ActionMode StartActionMode (ActionMode.Callback callback, int type){
      if (shouldBlockCopyPaste) {
        return null;
      } else {
        return super.StartActionMode(callback, type);
      }
    }

1
这对我有用! - Alberto M

1
这是一个艰难的问题。我在我的Android Studio 3.4.2中花了数小时进行研究和测试。
我提出了三个步骤: a) 在原始问题中使用setCustomSelectionActionModeCallback "解决方案",但是当您点击红色下拉时,它仍然会显示选择句柄和“剪贴板+全选”弹出窗口。 b) 为选择句柄创建一个空图像。我在res/drawable下创建了一个名为ic_empty.xml的文件。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
</shape>

c) 我已经在 style.xml 中为所有的 EditTexts 创建了一个样式。

在主题下面

 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        ...
        <item name="android:editTextStyle">@style/TStyle</item>
        ....
 </style>

因此,您可以通过将左、中、右选择手柄关联到空图像来定义您的样式:
   <style name="TStyle" parent="@android:style/Widget.EditText" >
        <item name="android:textSelectHandle">@drawable/ic_empty</item>
        <item name="android:textSelectHandleLeft">@drawable/ic_empty</item>
        <item name="android:textSelectHandleRight">@drawable/ic_empty</item>
    </style>

如果目标是从 API 23 开始,可以使用 setTextAppearanceEditText 中的文本中附加样式。但是,上述解决方案始终有效。
唯一剩下的问题就是我想要摆脱双击效果。它会选择文本中的一个单词,并带有粉色背景。然而,这相对无害,但很尴尬,因为它不需要用户交互。
一个小技巧是将高亮颜色设置为透明。
EditT.setHighlightColor(Color.TRANSPARENT)  // EditT is a EditText

0

我尝试了上面所有的答案,但是没有得到完整的解决方案。 如果你只想禁用PASTE选项,你可以尝试这个:

override fun getSelectionStart(): Int {
    for (element in Thread.currentThread().stackTrace) {
        if (element.methodName == "canPaste") {
            return -1
        }
    }
    return super.getSelectionStart()
}

这只是一个小技巧,但我没有找到更好的方法。

如果您想完全禁用菜单和光标,您可以尝试使用以下类代替EditText:

class MaskedCodeEditTextView : EditText {
    constructor(context: Context) : super(context) {
        init()
        blockContextMenu()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
        blockContextMenu()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
        init()
        blockContextMenu()
    }

    override fun getSelectionStart(): Int {
        for (element in Thread.currentThread().stackTrace) {
            if (element.methodName == "canPaste") {
                return -1
            }
        }
        return super.getSelectionStart()
    }

    private fun setInsertionDisabled() {
        try {
            val editorField = TextView::class.java.getDeclaredField("mEditor")
            editorField.isAccessible = true
            val editorObject = editorField[this]
            val editorClass = Class.forName("android.widget.Editor")
            val mInsertionControllerEnabledField =
                editorClass.getDeclaredField("mInsertionControllerEnabled")
            mInsertionControllerEnabledField.isAccessible = true
            mInsertionControllerEnabledField[editorObject] = false
        } catch (ignored: Exception) {
            // ignore exception here
        }
    }

    private fun blockContextMenu() {
        this.customSelectionActionModeCallback = ActionModeCallbackInterceptor()
        this.isLongClickable = false
        setOnClickListener { v: View? ->
            v?.let {
                if(!it.isFocused) {
                    requestFocus()
                } else {
                    clearFocus()
                    requestFocus()
                }
            }
        }
    }

    override fun isSuggestionsEnabled(): Boolean {
        return false
    }

    private fun init() {
        this.customSelectionActionModeCallback = ActionModeCallbackInterceptor()
        this.isLongClickable = false
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            setInsertionDisabled()
        }
        return super.onTouchEvent(event)
    }

    private inner class ActionModeCallbackInterceptor : 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) {}
    }
}
 

-1

试一下这个

mEditText.setClickable(false);
mEditText.setEnabled(false);

更新

尝试通过扩展Edittext来解决此问题,

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

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) {}
}
}

参考资料:https://dev59.com/nF4c5IYBdhLWcg3weqW9#28893714


1
如果一个 EditText 不可点击且未启用,那么从实际意义上讲它就不再是一个 EditText。它基本上只成为了一个 TextView。我仍然需要输入和选择文本。我只是不想显示系统上下文菜单。 - Suragch
@Suragch 哦,我以为它是一个EditText,因为在你的代码中它是一个EditText。 - Rashid
它仍然是一个EditText。我只是指的是你的解决方案(实际上禁用了上下文菜单),但它有不希望的副作用,即不允许用户输入或选择文本。 - Suragch
抱歉,在我的脑海中是TextView,但我仍在输入EditText。 - Rashid
你的更新看起来和我问题中的代码一样。有什么区别吗? - Suragch
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接