开发者

Creating a SoftKeyboard with Multiple/Alternate characters per key

I've followed the examples on developer.android.com regarding Input Methods and played with the SoftKeyboard sample application. These together give more than enough information regarding the creation of simple keyboard.

What I can't see in the API is the ability to create alternate / multiple characters per key which is available on the standard Keyboard (LatinIME Keyboard).

Creating a SoftKeyboard with Multiple/Alternate characters per key

The above image is the result of a long press on the "a" key. When you long press a key it's possible to populate a popup with alternate characters.

Creating a SoftKeyboard with Multiple/Alternate characters per key

It is also possible to give a popup hint on some keys which will prompt the user to press and hold a key in order to get the popup menu.

So far I haven't found a single source of information on how this is achieved, hopefully someone will be able to give me a head start, until then I'll follow the source code of the inbuilt keyboard and see if I can reverse engineer it.

Edit: Would help if developer.android.com 's link to the LatinIME Keyboard didn't link to a picture of a Sheep :) Actual source code for LatinIME.java.

Edit 2: More as a reference than anything else, this is the sequence I believe a usual longPress action goes through in order to show the popup keyboard in KeyboardView.java:

onTouchEvent()
onModifiedTouchEvent()
mHandkler.handleMessage() with MSG_LONGPRESS
openPopupIfRequired() 
onLongPre开发者_如何学JAVAss()

Edit 3:

I still haven't figured this out - How do you add label suggestions to keys? An answer suggests it isn't built into the API and indeed I haven't found the codeto do this. However the Keyboard on 2.3.4 (API 10) shows this functionality being implemented:

Creating a SoftKeyboard with Multiple/Alternate characters per key

Would very much like to figure out how IT does it but it isn't anywhere in the onDraw() method that I can see - which makes me believe it's being written outside of the KeyboardView element. I can't however find the layout file used to display the KeyboardView element on the inbuilt keyboard - If anyone knows where to find this perhaps that will give me the clue I need.

Edit 4: Moved key Preview question here as it's slightly off topic:

How do you disable the SoftKeyboard key preview window?


Implementing alternate key popup:

For each key you wish to have a popup keyboard you should define popupCharacters and popupKeyboard:

/res/xml/[Keyboard].xml

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

The popupKeyboard is an XML representation of the keyboard used in the popup containing the alternate keys:

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

Styling the alternate key popup:

If you want to change the layout/style of the popup (which defaults to @android:layout/ keyboard_popup_keyboard.xml) you can specify a android:popupLayout attribute which points to a layout file:

<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" />

Implementing Key Preview Overlay:

The only solution I've been able to knock together to show key previews (Without entirely rewriting the KeyboardView source code) is below:

Wrapping the <KeyboardView> tag with a <FrameLayout> with a height specified by multiplying the keyHeight by the amount of rows. Inside this tag I've simply created a LinearLayout to hold rows, then a LinearLayout for each row containing a TextView with a weight equal to the %p value specified for each <Key>:

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

And styled:

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

Which produces this:

Creating a SoftKeyboard with Multiple/Alternate characters per key

I won't be happy until I've managed to implement this in the same way as the System Keyboard does!


Judging from my own attempt at coding a softkeyboard I found out that:

  • Nice/bling features usually requires that you extend KeyboardView and basically write large parts of the drawing code. Unfortunately you cannot do this by overriding some key methods since almost everything is private. You might want to take a look (and borrow some code from:
    • (base)/core/java/android/inputmethodservice/KeyboardView.java (android core code repo)
    • (apps)/other/LatinIME/LatinKeyboardView.java (android core apps repo)

Note that the sheep on android.kernel.org is there to tell you that the repo is closed due to crackers but there are mirrors of the code elsewhere (lost the links unfortunately)

  • The base KeyboardView has no support for shadowed key hints, you must code your own KeyboardView to get a chance to override the onDraw() method.

Now on what you can do:

  • You can workaround this issue by providing pictures for the keys: use xml <Key ... android:keyIcon="@drawable/this_key_icon_file /> for this. Unfortunately, you'll most certainly have poor results for letters with this method (resolution issues).

  • You can use (and configure appearance of) popup keyboard that appears on long press.

Declare a keyboard template 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>

Declare string values containing the keys you want on this keyboard res/values/strings.xml:

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

Then, use both in your keyboard layout definition:

<Key android:codes="97" android:keyLabel="a"  
    android:popupKeyboard="@xml/kbd_popup_template"
    android:popupCharacters="@string/alternates_for_a" />
  • You can also use double-tap, triple-tap, ... feature to generate alternates for the key you're tapping. To do so, simply use a list for the android keycodes:

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

will produce 97='a' for single tap, 224='à' for double-tap and 230='æ' for triple-tap.

The duration to consider double-tapping is set to 800ms in android source code. It's unfortunately hardcoded (and a bit high, I feel).

Be aware that, when double-tapping, it basically sends an 'a' first, then, on the second tap it sends 'à'. Some apps, will not like this.


That popup keyboard with the close button is annoying when we have only one popup character. Simpler way is to override the onLongPress method of KeyboardView class like this.

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


If you want to have a text on top of your key, you can do it in onDraw() method in your class that overrides KeyboardView

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


For anyone trying to dismiss the popup keyboard by tapping outside its view area, I've had some luck putting a TouchListener on the KeyboardView inside the class extending InputMethodService

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


if you want to have a text on top of your key, you can do it in onDraw() method in your class that extends KeyboardView i did something like this maybe this could help someone

Creating a SoftKeyboard with Multiple/Alternate characters per key

 @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;


            }

        }

    }
}

adjust these axis according to your choice

int keyXAxis = 25;
int keyYAxis = 50;


Base on the answers here, what I did was:

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

and

When used KeyboardView changed it to MyKeyboardView, with this extra param - android:popupLayout="@layout/keyboard_popup_keyboard"

And this is keyboard_popup_keyboard.xml (important so there wont be a cancel button)

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜