如何默认显示Android带符号模式的键盘?

15
我有一个EditText组件,当你点击它时,Android键盘会出现,让用户输入文本。据我所知,所有Android软键盘都至少有字母模式(ABC)和符号模式(?123)。它们的默认视图是字母模式。
现在当点击EditText组件以显示键盘时,我想默认显示符号模式。用户仍然可以切换到字母模式。
有办法实现吗?如果有,如何实现?

我开始了悬赏并提出了额外的要求:这是一个患者ID字段,在95%的情况下包含数字,但有时也可能包含字母。最好的解决方法是什么? - OneWorld
几乎相同的问题。但是,在那里,要求输入数学方程式,这确实需要符号而不仅仅是数字。在Android上启动符号键盘模式,但不仅限于数字输入。 - OneWorld
5个回答

17

我发布这篇文章是因为我认为之前的答案都没有解决问题。问题中的截图并不对应于特定InputType的默认状态。因此,切换InputTypes并不能给你提供来自截图的布局。

(根据我的研究...)

符号输入的支持没有受到任何合约的管理。当创建自己的InputMethod时,完全可以省略符号。或者,他们可以添加分页支持以提供对100多个符号的访问。这能被合约所约束吗?也许。但目前还没有。

输入法框架不允许客户端和IME直接通信。所有的通信都发生在 InputMethodManagerInputConnection,是单向通道。然而,使用 ?123 切换到符号是一个内部事件,而不是一个定义好的状态/动作。客户端应用程序无法“切换到它”。没有公共(或隐藏的)API可以实现这一点。

InputType 对于IME来说意味着完全不同的东西。不确定为什么每个人都推荐使用它。你当然可以发现特定的 InputType 提供了大部分所需的键。但这与“默认显示Android带符号模式的键盘”不同。

可能的解决方法:

我们将创建一个自定义的 EditText。虽然我们不必这样做。但这会使所有东西都集中在一个地方,并避免了复制粘贴的噩梦。

public class CusEditText extends EditText {

    private final int mDefinedActionId;

    public CusEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
         
        // Corresponds to 'android:imeActionId' value
        mDefinedActionId = getResources().getInteger(R.integer.definedActionId);

        setOnEditorActionListener(new OnEditorActionListener() {
        
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                Log.i("CusEditText", "onEditorAction, actionId = " + actionId);

                // Only bother if (...)
                if (actionId == mDefinedActionId) {

                    // Check if current InputType is NUMBER
                    if ((getInputType() & InputType.TYPE_CLASS_NUMBER) != 0) {
                        // Toggle
                        setImeActionLabel("NUM", mDefinedActionId);
                        setInputType(InputType.TYPE_CLASS_TEXT);
                    } else {
                        // Current InputType is TEXT // Toggle
                        setImeActionLabel("ABC", mDefinedActionId);
                        setInputType(InputType.TYPE_CLASS_NUMBER);
                    }

                    // We've handled this
                    return true;
                }
            
                // Let someone else worry about this
                return false;
            }
        });
    }
}

接下来,我们需要定义definedActionId。打开或创建res/values/integers.xml并添加:

<integer name="definedActionId">-100</integer>

-100是一个任意值。我检查了EditorInfo和动作ID(IME_ACTION_XXXX),发现它们都大于等于0。-100似乎是一个不错的选择。

在xml中,你的布局将如下所示:

<com.your.packagename.CusEditText
    android:layout_width="blah"
    android:layout_height="blah"
    android:inputType="number"
    android:imeActionId="@integer/definedActionId"
    android:imeActionLabel="ABC"/>

<!-- Probably use @string resource in place of ABC -->

没有太多需要解释的。IME将在NUMBER模式下启动。它将显示ABC而不是勾号图标。点击后,我们会拦截actionId并在数字和文本输入之间切换。我们使用setInputType(...),因为它不仅更新了InputType,还可以带着更改重新启动IME。而setRawInputType(...)只会更新InputType

问题

正如您所看到的,这并不是一个完美的解决方案。如果用户在文本模式下关闭键盘(使用back按钮),则当他们再次打开时,键盘将保持在文本模式中。要进入数字模式,用户必须单击NUM。此外,在文本模式下,用户将看到NUM作为操作,以及?123选项。这并不会破坏任何内容,但确实影响了用户体验。

由于上述原因,我们无法防止在文本模式下出现?123。但是,我们可以尝试确保键盘始终以数字模式打开。下面是我们将如何做到这一点的大致草图。由于我们(开发人员)无法获悉键盘关闭或打开等事件,因此这并非易如反掌。更新后的CusEditText

public class CusEditText extends EditText {

    private final int mDefinedActionId;
    private long mLastEditorActionTime = 0L;

    public CusEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
         
        // Corresponds to 'android:imeActionId' value
        mDefinedActionId = getResources().getInteger(R.integer.definedActionId);

        setOnEditorActionListener(new OnEditorActionListener() {
        
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                Log.i("CusEditText", "onEditorAction, actionId = " + actionId);

                // Only bother if (...)
                if (actionId == mDefinedActionId) {

                    // setInputType(...) will restart the IME
                    // and call finishComposingText() 
                    // see below
                    mLastEditorActionTime = SystemClock.elapsedRealtime();

                    // Check if current InputType is NUMBER
                    if ((getInputType() & InputType.TYPE_CLASS_NUMBER) != 0) {
                        // Toggle
                        setImeActionLabel("NUM", mDefinedActionId);
                        setInputType(InputType.TYPE_CLASS_TEXT);
                    } else {
                        // Current InputType is TEXT // Toggle
                        setImeActionLabel("ABC", mDefinedActionId);
                        setInputType(InputType.TYPE_CLASS_NUMBER);
                    }

                    // We've handled this
                    return true;
                }
            
                // Let someone else worry about this
                return false;
            }
        });
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection inputConnection = super.onCreateInputConnection(outAttrs);

        return new CusInputConnectionWrapper(inputConnection, false);
    }

    private class CusInputConnectionWrapper extends InputConnectionWrapper {
        private CusInputConnectionWrapper(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        @Override
        public boolean finishComposingText() {
            Log.i("CICW", "finishComposingText");

            // Ignore finishComposingText for 1 second (1000L)
            if (SystemClock.elapsedRealtime() - mLastEditorActionTime > 1000L) {
                if ((getInputType() & InputType.TYPE_CLASS_NUMBER) == 0) {
                    // InputConnection is no longer valid.
                    // Switch back to NUMBER iff required
                    setImeActionLabel("ABC", mDefinedActionId);
                    setInputType(InputType.TYPE_CLASS_NUMBER);
                }
            }

            return super.finishComposingText();
        }
    }
}

再次说明,代码是自解释的。我们创建一个InputConnectionWrapper并监听finishComposingText()回调。如果我们手动在TEXTNUMBER之间切换,我们使用一个标志,因为finishComposingText()将自动调用。否则,我们检查输入类型是否设置为TEXT并将其更改为NUMBER。我不确定finishComposingText()是否是解释键盘打开/关闭的正确方法。在API 21上测试,使用原生Android,这似乎有效。需要进行更多测试。

我真的希望有人能提供比这更好、更健壮的解决方案,或者修改我的解决方法,使其看起来不像是一个解决问题的变通方法。

总结

任务是在现有的输入法引擎(IME)周围提供在 NUMBER 和 TEXT 输入模式之间切换的功能。第一种方法是在切换机制中使用imeActionLabel & imeActionId。这种方法在谷歌的键盘中运行良好(这是 imeActionLabel),但在三星的键盘中失败了 - 在竖屏模式下imeActionLabel无法显示(不带 extract)。可能的解决方法是在应用程序自己的 UI 中包含切换按钮。

即使在谷歌的键盘上,字母(文本)输入后切换回 NUMBER 时也无法显示出来。这个问题被修复了(至少在测试设备上),通过使用标志flagNoExtractUi,它可以防止 IME 进入横向全屏模式。

最终解决方案(待实现和测试)

  • IME 以 NUMBER 输入模式启动(95% 的用例涉及数字输入)
  • 在应用程序的 UI 中添加一个按钮(位于 EditText 旁边),用于在 NUMBER 和 TEXT 模式之间切换
  • 用户可以自由地从 NUMBER 切换到 TEXT 模式,没有任何限制。从 TEXT 切换回 NUMBER 需要确保未添加任何字母。
  • 在键盘关闭和重新打开时,InputType 被保留。例如:如果用户切换到 TEXT 模式并关闭键盘,则键盘将在 TEXT 模式下打开。InputType 不会重置。

有关尝试的方法的更多信息,请参阅此讨论线程

屏幕截图

默认(NUMBER)

输入图像描述

切换到TEXT

输入图像描述

录制视频链接


1
此时我应该分享我的实际需求:这是一个患者ID字段,在所有情况下95%包含数字,但有时也可能包含字母。在这种情况下,最好的解决方法是什么?顺便说一句,这是第一个精密的答案!谢谢! - OneWorld
@OneWorld 这个 95/5 的分配比例让人很沮丧 :)) 为了这 5% 的份额,所有这些工作!好吧,我已经想出了一个解决方法,使用自定义编辑器操作。如果您可以接受上面列出的问题,请放弃 CusEditText 的第二个实现(第二个 Java 代码块)。我对第二个实现不是很有信心,因为我只在一个设备和 API 级别上进行了测试。当然,您可以尝试一下,看看它的表现如何。 - Vikram
@OneWorld 你仍然可以使用 android:imeOptions="flagNavigateNext|flagNavigatePrevious" 来告诉输入法编辑器你需要前进/后退跳转。在这种情况下,长按 NUM/ABC 按钮将显示上一个/下一个选项。 - Vikram
@OneWorld 我已经添加了我们尝试过的简要总结,以及你将从这里开始的方向。在此之前,我实际上写了一个相当长的摘要 - 但是火狐浏览器崩溃了,把它带走了。所以,如果你感觉有什么遗漏,请务必让我知道。 - Vikram
2
@Vikram,这个答案提供的细节远远超出了文档给出的内容。真的很惊人,你能挖掘到所有这些信息。真的很酷。 - Raghunandan
显示剩余6条评论

2

我同意它是一个InputType。如果您想向用户仅显示数字,则需要在您EditText的xml文档中添加以下内容:

        android:inputType="number"

然而,如果您将其设置为数字,则用户必须输入数字。但是,您还可以添加其他类型,例如数字和电子邮件地址,如下所示:

        android:inputType="number|textEmailAddress"

请查看http://developer.android.com/reference/android/text/InputType.html以获取更多选项。您还可以在“inputType”下查看eclipse或android studio显示的内容。

4
因为答案不够简单易懂,所以被给予了负分。如果你将输入类型注册为数字(inputType),键盘将始终被锁定为数字键盘(12_KEY)- 无论你在inputType中添加什么附加标志,例如你的示例中的"textEmailAddress"。 - OneWorld

0
唯一的方法是通过设置您的EditTextinputType来实现。
如果您想在onCreate()(或自定义View构造函数内部)中设置此属性,可以使用setRawInputType() 方法:
mEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);

否则,如果您需要在onCreate()之后(或自定义View的构造函数之后)设置此属性,您可以使用方法setInputType()
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);

显然,您也可以在XML级别指定属性:

android:inputType="number|numberDecimal"

您可以尝试不同的标志来找到最佳的组合过滤器。


1
看不出与Deans和Ian的答案有什么不同,它们都不够充分。 - OneWorld
@OneWorld 我认为你不能再做更多了,输入类型是唯一欺骗键盘的方法(我的解决方案使用不同的标志,应该能匹配大多数输入)。 - bonnyz
@bonnys 如果我像你这样解释问题,我的客户可能无法理解。在这里需要更多的创意。如果Ian和Dean的答案足够好,我就不会开始悬赏了,显然这要求提供解决方案或者绕过直接解决不可能的问题。 - OneWorld

0

通过编程进行一些微小的调整,通常的流程是可以实现的。首先,您需要将editText设置为:

editText.setInputType(InputType.TYPE_CLASS_NUMBER);

然后你需要监听按键事件。在按下“#”键时,将InputType再次设置为InputType.TYPE_CLASS_TEXT。这应该像我一样有效。

editText.setOnKeyListener(new View.OnKeyListener() 
        {
         @Override
         public boolean onKey(View v, int keyCode, KeyEvent event) {
                                // TODO Auto-generated method stub
                                Log.d("KeyBoard", "Keyboard Test Key Hit");

         switch (keyCode) {
         KeyEvent.KEYCODE_POUND:
                                                                                      if(editText.setInputType(InputType.TYPE_CLASS_TEXT);
         {

         editText.setInputType(InputType.TYPE_CLASS_TEXT);
         return true;

     }

我回答过的同样的问题是:默认带有数字键盘的EditText,但允许字母字符


Vikram的解决方案也像你一样更改了“inputType”。我也可以使用井号。但是真正的问题在于:请查看我与Vikram的聊天 。最大的问题是三星设备处理输入类型类“number”。当设置此“inputType”时,所有字母都会从“EditText”中隐藏。感谢指向其他线程。 - OneWorld
@OneWorld 我在几个小时后关闭了聊天窗口。有机会请在那里给我发送一条消息。 - Vikram
它仍然链接在我对你回答的第一条评论中。 - OneWorld
@OneWorld 我知道。我假设如果你想进一步讨论,你会在这里留下评论。 - Vikram
这种方法很可能不起作用。软键盘通常不会发送键事件(或者至少被劝阻)。我不确定 KEYCODE_POUND 是否是其中的例外(我知道有些键是例外)。但是,KEYCODE_POUND 和这有什么关系呢?顺便说一下,硬件键盘会发送键事件。 - Vikram

0

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