用多个/备选字符创建软键盘

47
我已经按照developer.android.com上的示例学习了输入法,并尝试了SoftKeyboard示例应用程序。这些示例提供了关于创建简单键盘的足够信息。
但是在API文档中,我没有看到可以创建每个键的替代/多个字符的功能,这在标准键盘(LatinIME Keyboard)中是可用的。

enter image description here

上述图像是长按"a"键的结果。当您长按一个键时,可以弹出一个带有替代字符的弹出窗口。

enter image description here

还可以在某些键上给出弹出提示,提示用户按住一个键以获取弹出菜单。

到目前为止,我还没有找到关于如何实现这一点的单一信息来源,希望有人能够给我一个头绪,与此同时,我将遵循内置键盘的源代码,并看看是否可以进行反向工程。

编辑:如果developer.android.com链接到拉丁字母键盘的链接不是链接到一张Sheep的图片就好了:) LatinIME.java的实际源代码。

编辑2:更多作为参考而非其他,这是我认为通常的长按操作经过的顺序,以便在KeyboardView.java中显示弹出键盘:

onTouchEvent()
onModifiedTouchEvent()
mHandkler.handleMessage() with MSG_LONGPRESS
openPopupIfRequired() 
onLongPress()

编辑 3:

我仍然没有搞清楚 - 如何将标签建议添加到键中?有一个答案表明它没有内置到 API 中,事实上我也没有找到相关的代码。然而,在 2.3.4(API 10)上的键盘显示了这个功能的实现:

enter image description here

我非常希望能够弄清楚IT是如何做到的,但我在onDraw()方法中没有找到任何信息,这让我相信它是在KeyboardView元素之外编写的。然而,我找不到用于在内置键盘上显示KeyboardView元素的layout文件-如果有人知道在哪里找到这个文件,也许那会给我提供所需的线索。

编辑4:将键盘预览问题移到此处,因为它略微偏题:

如何禁用SoftKeyboard键预览窗口?


你找到实现自定义弹出窗口的解决方案了吗?请更新。 - Mateen Chaudhry
7个回答

57

实现替代键弹出:

对于您希望有弹出键盘的每个键,您应该定义 popupCharacterspopupKeyboard

/res/xml/[Keyboard].xml

<Key android:keyLabel="("
    android:popupKeyboard="@xml/keyboard_popup_template"
    android:popupCharacters="[{&lt;" />

popupKeyboard是一个XML表示键盘的弹出窗口,其中包含备用键:

/res/xml/keyboard_popup_template.xml

<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="10%p"
    android:horizontalGap="0px"
    android:verticalGap="0px"
    android:keyHeight="56dp">
</Keyboard>

样式化备选键弹出窗口:

如果您想要更改弹出窗口的布局/样式(默认情况下为@android:layout/ keyboard_popup_keyboard.xml),您可以指定一个android:popupLayout属性,该属性指向一个布局文件:

<android.inputmethodservice.KeyboardView
    android:id="@+id/keyboard"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:background="#FF272727"
    android:popupLayout="@layout/keyboard_popup_keyboard" />

实现按键预览悬浮窗:

我能够想到的唯一解决方案(而不必完全重写 KeyboardView 源代码)如下:

用一个高度等于行数乘以按键高度的 <FrameLayout> 包裹 <KeyboardView> 标签。在这个标签内,我创建了一个 LinearLayout 来容纳每一行,然后为每一行又创建了一个 LinearLayout,其中包含一个 TextView,其权重等于每个 <Key> 指定的 %p 值。

<TextView android:text="!" style="@style/Custom.Widget.KeyboardKeyOverlay"  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="10"/>

并加上样式:

<style name="CustomTheme.Widget.KeyboardKeyOverlay">
    <item name="android:background">@android:color/transparent</item>
    <item name="android:textColor">#FFAAAAAA</item>
    <item name="android:paddingRight">6dp</item>
    <item name="android:paddingTop">4dp</item>
    <item name="android:textSize">10sp</item>
    <item name="android:gravity">right</item>
    <item name="android:textStyle">bold</item>
</style>         

这将产生以下结果:

在此输入图像描述

除非我能像系统键盘一样实现它,否则我不会满意!


@Graeme, 我不太理解如何实现键盘预览叠加。 我需要在Framelayout中包含keyboardView吗?如果是的话,那么键盘行应该如何声明? 请问您能否指导我开发一个好用的输入法? 谢谢。 - Ashwin N Bhanushali
@Graeme,你能提供你解决方案的源代码吗?顺便说一句,不错的解决方法! - José Silva
有没有办法替换弹出键盘上的默认关闭按钮?我知道你可以改变布局,但我正在尝试创建一种方式,让用户通过触摸其区域之外的某个地方来关闭弹出键盘。 - Sammy T
请问您能否提供完整的解决方案?我在尝试覆盖多个按键的自定义键盘时出现了问题。 - user1510006
很棒的答案。不幸的是,此链接已失效:@android:layout/keyboard_popup_keyboard.xml) 您正在展示如何调用自定义弹出布局; 您能否添加一个自定义样式文件的示例。我不需要什么花哨的东西,只是想让我的弹出窗口具有与我的主(自定义)键盘相同的背景颜色和边框颜色。谢谢。 - Martin Zaske
@android:layout/keyboard_popup_keyboard.xml的工作链接为:https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/keyboard_popup_keyboard.xml - zsd

15

根据我编写软键盘的尝试,我发现:

  • 好看/华丽的特性通常需要你扩展KeyboardView并基本上编写大部分绘图代码。不幸的是,你不能通过覆盖一些关键方法来实现这一点,因为几乎一切都是私有的。你可能想要查看(并从以下位置借用一些代码):
    • (base)/core/java/android/inputmethodservice/KeyboardView.java (Android核心代码仓库)
    • (apps)/other/LatinIME/LatinKeyboardView.java (Android核心应用程序仓库)

请注意,Android.kernel.org上的羊是告诉你该仓库由于黑客袭击而关闭了,但是在其他地方有该代码的镜像(遗憾的是我忘记了链接)。

  • 基本的KeyboardView没有支持阴影按键提示,你必须编写自己的KeyboardView才能有机会覆盖onDraw()方法。

现在讲讲你可以做什么:

  • 你可以通过为键提供图片来解决这个问题:使用XML <Key ... android:keyIcon="@drawable/this_key_icon_file />。不幸的是,使用此方法对于字母键可能会导致分辨率问题,结果很差。

  • 你可以使用(并配置外观)长按后出现的弹出式键盘。

声明一个键盘模板res/xml/kbd_popup_template.xml

<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="10%p"
    android:horizontalGap="0px"
    android:verticalGap="0px"
    android:keyHeight="@dimen/key_height">
</Keyboard>

res/values/strings.xml中声明包含您想要添加到此键盘上的键的字符串值:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <string name="alternates_for_a">àáâãäåæ</string>
</ressources>

然后,在您的键盘布局定义中同时使用它们:

<Key android:codes="97" android:keyLabel="a"  
    android:popupKeyboard="@xml/kbd_popup_template"
    android:popupCharacters="@string/alternates_for_a" />
  • 您还可以使用双击、三击等功能来为您点击的键生成备选项。要这样做,只需在Android按键代码中使用列表

    <Key android:codes="97,224,230" .../>

将会产生以下结果:单击时97='a',双击时224='à',三击时230='æ'。

在Android源代码中,考虑双击的时间间隔设置为800毫秒。不幸的是,这是硬编码的(我感觉有点长)。

请注意,当双击时,它基本上首先发送 'a',然后在第二次敲击时发送'à'。一些应用程序可能不喜欢这种操作方式。


很不幸的是,当我按照这个解决方案操作时,弹出字符会出现一个带有'x'(关闭按钮)的窗口。而且除非我选择它或者选择一个字符,否则它不会消失。我该如何避免出现这个'x'按钮呢? - IgorGanapolsky
抱歉,Igor G.,我无法帮助你:这在我的Froyo和ICS上都没有发生过。也许这是设备/供应商相关的问题? - Laurent'
谢谢。但是我已经尝试过不同的设备和不同版本的操作系统。所以可能只是我的代码的问题。 - IgorGanapolsky

12

当我们只有一个弹出字符时,带有关闭按钮的弹出式键盘非常烦人。更简单的方法是像这样覆盖KeyboardView类的onLongPress方法。

@Override
protected boolean onLongPress(Key key) {
    if (key.codes[0] == '1') {
        getOnKeyboardActionListener().onKey('!', null);
        return true;
    }
}

8
如果你想在按键上方放置文本,你可以在覆盖KeyboardView的类中的onDraw()方法中实现。
 @Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    ...
    Paint paint = new Paint();
    paint.setTextAlign(Paint.Align.CENTER);
    paint.setTextSize(18);
    paint.setColor(Color.WHITE);
    //get all your keys and draw whatever you want
    List <Keyboard.Key> keys = getKeyboard().getKeys();
    for(Keyboard.Key key: keys) {
        if(key.label != null) {

            if (key.label.toString().equals("q") || key.label.toString().equals("Q"))
                canvas.drawText(String.valueOf(1), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("w") || key.label.toString().equals("W"))
                canvas.drawText(String.valueOf(2), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("e") || key.label.toString().equals("E"))
                canvas.drawText(String.valueOf(3), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("r") || key.label.toString().equals("R"))
                canvas.drawText(String.valueOf(4), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("t") || key.label.toString().equals("T"))
                canvas.drawText(String.valueOf(5), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("y") || key.label.toString().equals("Y"))
                canvas.drawText(String.valueOf(6), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("u") || key.label.toString().equals("U"))
                canvas.drawText(String.valueOf(7), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("i") || key.label.toString().equals("I"))
                canvas.drawText(String.valueOf(8), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("o") || key.label.toString().equals("o"))
                canvas.drawText(String.valueOf(9), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else if (key.label.toString().equals("p") || key.label.toString().equals("P"))
                canvas.drawText(String.valueOf(0), key.x + (key.width / 2) + 10, key.y + 25, paint);

            else
            {}
        }
    }
}

1
只是一个改进建议,针对多个if-else语句:使用switch(key.codes[0]) { ... }语句来加快代码速度。 - ilomambo
是的,有多种方法可以改进这段代码,例如使用equalsIgnoreCase()等,重点是要展示您可以通过编程方式绘制该文本。无论如何,感谢您的建议。 - Fedor Tsyganov
1
当从键盘按下并释放任何键时,onDraw方法会被调用两次,这不会在低端设备上变得昂贵吗? - Mateen Chaudhry

6

对于试图通过在视图区域外部轻触来解除弹出式键盘的任何人,我尝试在扩展InputMethodService的类中,在KeyboardView上放置TouchListener时,取得了一些成功。

public class YourIME extends InputMethodService{
    @Override 
    public View onCreateInputView() {
        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(R.layout.input, null);
        setLatinKeyboard(mQwertyKeyboard);

        mInputView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {

                if(motionEvent.getAction() == MotionEvent.ACTION_DOWN) {                        
                    mInputView.closing(); // Close popup keyboard if it's showing
                }
                return false;
            }
        });

        return mInputView;
    }
// The rest of your ime ...
}

2
这里只有一个问题,如果你在键盘弹出窗口之外点击,它将输出你点击的字母,需要添加一个条件,只有在弹出窗口打开时才会发生这种情况。 - ChRoNoN
除了这个答案之外,未记录的方法KeyboardView.handleBack()可以关闭弹出键盘。http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/inputmethodservice/KeyboardView.java#1423 当ACTION_UP发生时,可以从扩展自KeyboardView的类的onTouchEvent()中调用它。 - Ian
我遇到了这个问题,但好像没有什么作用。setOnTouchListener和setOnKeyboardActionListener会不会发生冲突呢? - Matthew Morrone

2
如果您想在按键上方显示文本,可以在扩展KeyboardView类的类中的onDraw()方法中实现。我做了这样的事情,也许对某些人有帮助。

enter image description here

 @Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.d("LatinKeyboardView", "onDraw");

    Paint paint = new Paint();
    paint.setTextAlign(Paint.Align.CENTER);
    paint.setTextSize(30);
    paint.setColor(Color.LTGRAY);

    List<Key> keys = getKeyboard().getKeys();
    for (Key key : keys) {
        if (key.label != null) {
            switch (key.codes[0]) {

                //qQ
                case 81:
                case 113:
                case 1602:
                case 1618:
                    canvas.drawText(String.valueOf(1), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //wW
                case 87:
                case 119:
                case 1608:
                case 1572:
                    canvas.drawText(String.valueOf(2), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //eE
                case 69:
                case 101:
                case 1593:
                case 1617:
                    canvas.drawText(String.valueOf(3), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;


                //rR
                case 82:
                case 114:
                case 1585:
                case 1681:
                    canvas.drawText(String.valueOf(4), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //tT
                case 84:
                case 116:
                case 1578:
                case 1657:
                    canvas.drawText(String.valueOf(5), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //yY
                case 89:
                case 121:
                case 1746:
                case 1552:
                    canvas.drawText(String.valueOf(6), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //uU
                case 85:
                case 117:
                case 1569:
                case 1574:
                    canvas.drawText(String.valueOf(7), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //iI
                case 73:
                case 105:
                case 1740:
                case 1648:
                    canvas.drawText(String.valueOf(8), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //oO
                case 79:
                case 111:
                case 1729:
                case 1731:
                    canvas.drawText(String.valueOf(9), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //pP
                case 80:
                case 112:
                case 1662:
                case 1615:
                    canvas.drawText(String.valueOf(0), key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;


                //aA
                case 65:
                case 97:
                case 1575:
                case 1570:
                    canvas.drawText("@", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //sS
                case 83:
                case 115:
                case 1587:
                case 1589:
                    canvas.drawText("#", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //dD
                case 68:
                case 100:
                case 1583:
                case 1672:
                    canvas.drawText("$", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //fF
                case 70:
                case 102:
                case 1601:
                case 1613:
                    canvas.drawText("%", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //gG
                case 71:
                case 103:
                case 1711:
                case 1594:
                    canvas.drawText("&", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //hH
                case 72:
                case 104:
                case 1726:
                case 1581:
                    canvas.drawText("-", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //jJ
                case 74:
                case 106:
                case 1580:
                case 1590:
                    canvas.drawText("+", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //kK
                case 75:
                case 107:
                case 1705:
                case 1582:
                    canvas.drawText("(", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //lL
                case 76:
                case 108:
                case 1604:
                case 1614:
                    canvas.drawText(")", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //zZ
                case 90:
                case 122:
                case 1586:
                case 1584:
                    canvas.drawText("*", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //xX
                case 88:
                case 120:
                case 1588:
                case 1679:
                    canvas.drawText("\"", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //cC
                case 67:
                case 99:
                case 1670:
                case 1579:
                    canvas.drawText("\'", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //vV
                case 86:
                case 118:
                case 1591:
                case 1592:
                    canvas.drawText(":", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //bB
                case 66:
                case 98:
                case 1576:
                case 1616:
                    canvas.drawText(";", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;

                //nN
                case 78:
                case 110:
                case 1606:
                case 1722:
                    canvas.drawText("!", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;
                //mM
                case 77:
                case 109:
                case 1605:
                case 1611:
                    canvas.drawText("?", key.x + (key.width - keyXAxis), key.y + keyYAxis, paint);
                    break;


            }

        }

    }
}

根据您的选择调整这些轴。
int keyXAxis = 25;
int keyYAxis = 50;

2
根据这里的答案,我所做的是:

public class MyKeyboardView extends KeyboardView {
    private Keyboard.Key longPressedKey = null;

    public MyKeyboardView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyKeyboardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(35);
        paint.setColor(Color.BLACK);
        //get all your keys and draw whatever you want
        List<Keyboard.Key> keys = getKeyboard().getKeys();
        for (Keyboard.Key key : keys) {
            if (key.popupCharacters != null && key.popupCharacters.length() > 0) {
                canvas.drawText(key.popupCharacters.toString(), key.x + (key.width / 2) + 10, key.y + 30, paint);
            }
        }
    }


    @Override
    protected boolean onLongPress(Keyboard.Key key) {
        if (key.popupCharacters != null && key.popupCharacters.length() > 0)
            longPressedKey = key;
        return super.onLongPress(key);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (longPressedKey != null) {
                // TODO: Try detecting what key the finger is above if there are many popupCharacters
                getOnKeyboardActionListener().onKey((longPressedKey.popupCharacters.charAt(0)), null);
                dismissPopupKeyboard();
                longPressedKey = null;
            }
        }
        return true; // Required for recieving subsequent events (ACTION_MOVE, ACTION_UP)
    }

    private void dismissPopupKeyboard() {
        // Because of KeyboardView.dismissPopupKeyboard() is private, we are doing this trick (KeyboardView.click() calls dismissPopupKeyboard()).
        // TODO: Make this like a normal human. We will need to create the popup on our own like the way the keyboard does it.
        onClick(new View(getContext()));
    }

当使用KeyboardView时,将其更改为MyKeyboardView,并添加此额外参数 - android:popupLayout="@layout/keyboard_popup_keyboard"

这是keyboard_popup_keyboard.xml(重要的是,这样就不会有取消按钮)

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    >

    <android.inputmethodservice.KeyboardView
        android:id="@android:id/keyboardView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/popup_key_color"
        android:keyPreviewLayout="@layout/key_preview"
        android:keyPreviewOffset="0px"
        android:keyPreviewHeight="@dimen/key_preview_height"
        android:keyTextColor="@color/darkGray"
        android:keyBackground="@drawable/key_background_selector"
        android:shadowRadius="0.0"
        android:horizontalGap="0px"
        android:verticalGap="0px" />
</LinearLayout>

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