Android: Non-Keyboard IME
Asked Answered
B

2

8

Im trying to create an IME for android that is not a traditional keyboard (rows of keys corresponding to the different letters) and I am having trouble finding helpful resources on how to do so since all of the code examples in the SDK use a Keyboard API and it's built in functions.

I've designed the interface of the IME in an XML file as though it were simply an Activity within in application but I'm getting lost in their demos since their Keyboards are simply objects that are built from a different style of XML structure.

If anyone has a link to an open source project that does not use the tradional keyboard structure or could simply guide me to displaying the UI that I've already constructed onto the IME window, I think I can figure the rest out.

If you need any more specfic information, please feel free to ask. Thanks in advance.

Betsybetta answered 23/6, 2011 at 2:13 Comment(1)
Please help me how to set up the your own keyboard? .I have done using separate application.I want to show small keyboard near the TextBox (Left or right).This is my question. #7358376 I have done my own keyboard using Android API demo...Opiate
A
6

The SoftKeyboard sample is in fact structured similarly to what you need, though you don’t know it. Let’s break it down — here’s a complete stub to act as an input method service and use the standard XML keyboard:

import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;

public class MyInputMethod extends InputMethodService {
    private Keyboard mKeyboard;

    @Override public void onInitializeInterface() {
        mKeyboard = new Keyboard(this, R.xml.qwerty);
    }

    @Override public View onCreateInputView() {
        mInputView = (KeyboardView) getLayoutInflater().inflate(
                R.layout.input, null);
        mInputView.setKeyboard(mKeyboard);
        return mInputView;
    }
}

Note that two XML documents are named. The first, res/xml/qwerty.xml, defines the layout so that the KeyboardView class knows how to draw the keyboard.

But the layout which it inflates, res/layout/input.xml, consists of this (simplified):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
    <android.inputmethodservice.KeyboardView android:id="@+id/keyboard" />
</LinearLayout>

This is all you need to declaratively create a view! Creating a stand-alone View isn’t much different from creating an Activity. You don’t have the standard activity lifecycle, but both environments give you access to XML layouts. All you need to do is use the inflater, reference any subviews you need, and then return the main view.

So hopefully you’ll be able to inflate your layout after thinking about this.


You don’t even need to use XML layouts if your layout is simple enough. If your input method can consist entirely of a single view, you can just instantiate it directly in onCreateInputView. Here’s a complete stub input method service that doesn’t use any XML:

public class MyInputMethod extends InputMethodService {
    MyView view;

    @Override
    public View onCreateInputView() {
        return view = new MyView(this);
    }

}

(Of course the boilerplate in the manifest and res/xml/method.xml files would still be there.)

Aubergine answered 23/6, 2011 at 3:8 Comment(5)
Thanks so much, I got so caught up in all of their keyboard classes and methods that I overlooked such a simple oversight.Betsybetta
Please help me this question.If you have successfully done that ...please help me #7358376Opiate
I imagine this thread is dead, but what exactly is required in the manifest?Jaqitsch
@Jaqitsch I think it’s just a <service> that responds to the android.view.InputMethod action, but check in the SoftKeyboard sample to be sure.Aubergine
@JoshLee I'm reading your solution in order to understand how to use a RelativeLayout for a custom keyboard instead of the default keyboard layout but I am very confused. Could you post a code example where there is the normal keyboard.xml file and there is a Relative Layout file for the actual design of the keyboard?Viewy
B
0

The question is ancient, but for anyone looking for a more recent solution, I just published a barebones non-keyboard IME for Android to Github: https://github.com/jskubick/MiNKI (Minimal Non-Keyboard Ime). This is basically the answer I wish I'd had available when I first stumbled across this question 2 days ago :-)

Details:

Since StackOverflow frowns upon link-only answers, here's how to re-create it:

Create a new Android Studio project.

  1. Name it "MiNKI"
  2. Use the template for "Empty Project", and name the main class' package "example.ime.minki" (the two .java files I listed below go there).

You can always rename the package and project later, but following these two steps will increase the likelihood of everything working on the first try.

Add the following to AndroidManifest.xml:

Don't forget the meta-data for android.view.im... if you omit it, Android won't recognize it as a valid IME.

        <service android:name="example.ime.minki.MinkiService"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_INPUT_METHOD"
            android:exported="true">
            <intent-filter>
                <action android:name="android.view.InputMethod" />
            </intent-filter>
            <meta-data android:name="android.view.im"
                android:resource="@xml/method" />
        </service>

res/xml/method.xml:

Note that I specified imeSubtypeMode, but not locale or language. I'm ultimately writing an IME that's language-agnostic (or at least, language-that-uses-the-Roman-alphabet agnostic), and didn't want to risk encouraging others to copy a solution that might someday leave someone in Britain, Canada, or elsewhere swearing violently because a future paternalistic, dystopian version of Android pointlessly locked them out of a keyboard specified as "en_US"

<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
    <subtype android:imeSubtypeMode="keyboard" />
</input-method>

res/layout/minki.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:theme="@style/Theme.AppCompat.DayNight">

    <example.ime.minki.MinkiView
        android:id="@+id/minkiView"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#aabbaa"
        />
</LinearLayout>

src/main/java/example/ime/minki/MinkiService.java:

You'll probably want to replace everything in onTouch()... I just put the most minimal stuff possible to detect the following single strokes, and omitted all the code that would normally draw their path below your finger because it adds massive amounts of code and makes it harder to understand the IME code itself.

There's nothing special about implementing the OnTouchListener() in MinkiService. I register it as a listener in onCreateInputView(). Notice how I handled normal letters, the enter key, and backspace. None of this is necessarily "best practice"... but it works.

Strokes it recognizes (should be at least 50-100 DP long)

  • left to right: the letter 'x' (or X, if shift is active)
  • right to left: backspace
  • bottom to top: toggle shift
  • top to bottom: the string "taco" or "cat", depending upon whether or not shift is active.
  • approximately 45-degree falling slope ("\"): Enter key
package example.ime.minki;

import static android.view.KeyEvent.ACTION_UP;

import android.inputmethodservice.InputMethodService;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;

public class MinkiService extends InputMethodService implements View.OnTouchListener {
    private static final String TAG = "MinkiService";
    private static int counter = 0;
    private InputMethodManager inputMethodManager;

    private int downAtX = 0;
    private int downAtY = 0;

    private boolean isShifted = false;

    @Override
    public void onCreate() {
        super.onCreate();
        inputMethodManager = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
    }

    @Override
    public View onCreateInputView() {
        Log.i(TAG, "about to inflate and return R.layout.minki");
        View v = getLayoutInflater().inflate(R.layout.minki, null);
        MinkiView mv = v.findViewById(R.id.minkiView);
        v.setOnTouchListener(this);
        return v;
    }

    @Override
    public void onStartInput(EditorInfo info, boolean restarting) {
        super.onStartInput(info, restarting);
        Log.i(TAG, "onStartInput() called, counter=" + ++counter);
        isShifted = false;
    }

    @Override
    public void onFinishInput() {
        super.onFinishInput();
        Log.i(TAG, "onFinishInput() called, counter=" + counter);
    }

   
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getActionMasked()==MotionEvent.ACTION_DOWN) {
            downAtX = Math.round(motionEvent.getX());
            downAtY = Math.round(motionEvent.getY());
            Log.i(TAG, "ACTION_DOWN! (" + downAtX + "," + downAtY + ")");
        }
        else if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
            Log.i(TAG, "ACTION_UP (" + motionEvent.getX() + "," + motionEvent.getY() + ")" );

            int xDiff =Math.round(motionEvent.getX()) - downAtX ;
            int yDiff = Math.round(motionEvent.getY()) - downAtY;
            int slope = (xDiff == 0) ? Integer.MAX_VALUE : (int) ((yDiff / (float)xDiff) * 100);

            Log.i(TAG, "xDiff=" + xDiff + ", yDiff=" + yDiff + ", slope=" + slope);


            if (Math.abs(xDiff) < 50) {
                if (yDiff < -100) {
                    isShifted = !isShifted;
                    Toast.makeText(this, isShifted ? "shifted" : "unshifted", Toast.LENGTH_SHORT).show();
                    return true;
                }
                else if (yDiff > 100) {
                    commitText(isShifted ? "CAT" : "taco");
                    return true;
                }
            }

            // horizontal stroke
            if ((Math.abs(slope) < 30) && (Math.abs(xDiff) > 100)){

                if (xDiff < -100)
                    getCurrentInputConnection().deleteSurroundingText(1,0); // backspace
                else if (xDiff > 100) {
                    getCurrentInputConnection().commitText( isShifted ? "X" : "x", 1);
                    isShifted = false;
                }
            }

            else if ((slope > 50) && (slope < 300)) {
                getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
                getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
                Log.i(TAG, "hit return");
            }

            downAtX = 0;
            downAtY = 0;
        }
        return true;
    }

    private void commitText(CharSequence text) {
        Log.i(TAG, "committing '" + text + "'");
        Log.i(TAG, String.valueOf(getCurrentInputConnection().commitText(text, 1)));
    }
}

src/main/java/example/ime/minki/MinkiView.java:

package example.ime.minki;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class MinkiView extends View {
    public static final String TAG = "MinkiView";

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

}

Important!

This example shouldn't be taken as showing any "best practice". Its whole point is to get you past the first roadblock, and give you a little bit of easy gratification by getting an IME compiled by you to install and work.

I deliberately stripped it to the bone to make it clear which things MUST be part of an Android Studio project for a non-keyboard IME. Assuming I haven't overlooked anything, adding those 5 things to a newly-created Android Studio project SHOULD be all you need to do to get it to work.

Bobine answered 12/11, 2022 at 2:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.