禁用EditText上下文菜单

时间:2017-01-16 09:33:22

标签: android android-edittext contextmenu mongolian-vertical-script

我正在为传统蒙古人制作一个垂直EditText。我通过在旋转的EditText内嵌入略微修改的ViewGroup来成功实现它。我需要创建一个完全自定义的上下文菜单,因为系统不支持垂直文本,并且在ViewGroup旋转时也不会旋转。所以我想完全禁用系统上下文菜单。

请注意,这与仅仅尝试禁用复制/粘贴/等的这些问题不同:

虽然我没有在模拟器中显示上下文菜单,但我将其显示在我的Android 5.0.2小米手机中。

我试过了:

我对hacks持开放态度,但我需要它能够在各种设备上持续工作。 Mark Murphy(一个Commons Guy)wrote一段时间回复了另一个尝试做类似事情的用户:

  

我怀疑即使你想出答案,它也行不通   跨设备。设备制造商倾向于推出他们的产品   拥有"上下文菜单"对于EditText,击败开发者'尝试添加   项目进入该上下文菜单。我的猜测是试图阻止它   上下文菜单会有类似的结果。

我运气不好吗?

我现在唯一能想到的就是从头开始重写TextViewEditText(好吧,修改Android源代码)。我知道其他人做了类似的事,但他的代码不是开源的。在我采取这一重大步骤之前,我想尝试在Stack Overflow上寻求更简单的解决方案。

更新:我过去两天一直在尝试修改TextView源代码,看起来像是一个6个月的项目。这是一大堆相互关联的课程。我需要另一种解决方案,但我没有想法。

MVCE

这是我能想到的最简单的方法来重现问题。我的自定义EditText没有必要。通过替换默认项目Hello World' s EditText,布局有一个TextView。我将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

在运行Android 5.0的小米MIUI手机上,我得到了所有情况下的上下文菜单(光标处理点击,长按,双击)。

更新

Aritra Roy的解决方案是在模拟器中,在他测试的其他设备上以及我的设备上工作。我接受了他的答案,因为它解决了我原来的问题。唯一的负面影响是文本选择也被禁用​​。

8 个答案:

答案 0 :(得分:21)

您需要做三件事。

第1步

您可以通过从这些方法返回false来禁用上下文菜单,

mEditEext.setCustomSelectionActionModeCallback(new ActionMode.Callback() {

            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false;
            }

            public void onDestroyActionMode(ActionMode mode) {                  
            }

            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                return false;
            }

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

第2步

还需要禁用EditText中的长按。

mEditText.setLongClickable(false);

或执行此操作,XML中的android:longClickable="false"

第3步

现在,您需要防止在单击手柄时显示菜单。解决方案很简单,

1)扩展EditText类,

2)覆盖isSuggestionsEnabled()并返回false

3)创建canPaste()方法并返回false。这是方法隐藏。

快速解决方案

如果您不想手动完成所有这些操作。这是一个自定义的EditText类,您可以使用它来快速完成此操作。但是我仍然建议你一次完成这些步骤,以了解事情是如何运作的。

public class MenuHidingEditText extends EditText {
    private final Context mContext;

    public MenuHidingEditText(Context context) {
        super(context);
        this.mContext = context;

        blockContextMenu();
    }

    public MenuHidingEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;

        blockContextMenu();
    }

    public MenuHidingEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;

        blockContextMenu();
    }

    private void blockContextMenu() {
        this.setCustomSelectionActionModeCallback(new BlockedActionModeCallback());
        this.setLongClickable(false);
        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                MenuHidingEditText.this.clearFocus();
                return false;
            }
        });
    }

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

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

    @Override
    public boolean isSuggestionsEnabled() {
        return false;
    }

    private class BlockedActionModeCallback implements ActionMode.Callback {

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

答案 1 :(得分:3)

我已为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();
}

答案 2 :(得分:2)

解决方案非常简单

rails generate active_admin:resource MyModel

enter image description here

答案 3 :(得分:1)

mEditText.setLongClickable(false);

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

答案 4 :(得分:1)

这是一个难题。 我花了很多小时在我的 Android Studio 3.4.2 中进行研究和测试。

我建议3个步骤:

a)原始问题中的setCustomSelectionActionModeCallback "solution" 但是,当您单击红色下拉菜单时,它始终显示选择手柄(光标下方的红色下拉菜单)和“剪贴板+全选”弹出窗口。

b)为选择的手柄创建一个空白图像。我在ic_empty.xml下创建了一个名为res/drawable的文件。

<?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>

因此,您可以定义样式,为lef,中间和右选择手柄关联一个空图像:

   <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

答案 5 :(得分:0)

这是阻止复制粘贴菜单以任何方式,形状或形式出现的方式。这个错误真的让我发疯,就像任何三星的bug一样,你知道它的代码,但你也知道他们很快就会解决它。无论如何,这里有奇妙的墙......

  1. 检查Android.Build.Model.toLowerCase()。startsWith(&#39; sm-g930&#39;)。不匹配整个字符串,最后一个字母是次要版本标识符。我将这个布尔值存储在了稍后出现的shouldBlockCopyPaste变量中。

  2. 如果匹配,则要阻止复制粘贴菜单显示。这就是你的实际行动方式!!!

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

    function html2json() {   
      var otArr = [];
      var tblHeaders = Array.from($('table thead tr')
        .children())
        .map(header => $(header).text());  
      var tbl2 = $('table tbody tr').each(function(e) {        
        const values = Array.from($(this).children());
        const row = {};
        for (let i = 0; i < tblHeaders.length; i++){        
            row[tblHeaders[i]] = $(values[i]).text();
        }
        otArr.push({
            [e+1]: row
        })      
      })
      json = JSON.stringify(otArr);  
      return json;
    }
    

答案 6 :(得分:0)

我尝试了上述所有答案,但没有得到完整的解决方案。 如果您只想禁用PASTE选项,则可以尝试以下操作:

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

这只是一个hack,但是我没有发现更好的东西。

如果您想完全禁用菜单和光标,可以尝试使用以下类而不是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) {}
    }
}
 

答案 7 :(得分:-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://stackoverflow.com/a/28893714/5870896