Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture soft keyboard input in a View?

Tags:

android

I have a subclassed View that pops up the keyboard when it receives a 'touch up' in onTouchEvent. It shows this by requesting focus, retrieving the InputMethodManager, and then calling showSoftInput.

Now I need to figure out how to capture the tapped letters of the soft keyboard, as they are pressed. I am currently only getting a response when the Next/Done button is pressed on the soft keyboard.

Here is my class:

public class BigGrid extends View {      private static final String TAG = "BigGrid";      public BigGrid(Context context) {         super(context);         setFocusableInTouchMode(true); // allows the keyboard to pop up on                                        // touch down          setOnKeyListener(new OnKeyListener() {             public boolean onKey(View v, int keyCode, KeyEvent event) {                 Log.d(TAG, "onKeyListener");                 if (event.getAction() == KeyEvent.ACTION_DOWN) {                     // Perform action on key press                     Log.d(TAG, "ACTION_DOWN");                     return true;                 }                 return false;             }         });     }      @Override     public boolean onTouchEvent(MotionEvent event) {         super.onTouchEvent(event);         Log.d(TAG, "onTOUCH");         if (event.getAction() == MotionEvent.ACTION_UP) {              // show the keyboard so we can enter text             InputMethodManager imm = (InputMethodManager) getContext()                     .getSystemService(Context.INPUT_METHOD_SERVICE);             imm.showSoftInput(this, InputMethodManager.SHOW_FORCED);         }         return true;     }      @Override     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {         Log.d(TAG, "onCreateInputConnection");          BaseInputConnection fic = new BaseInputConnection(this, true);         outAttrs.actionLabel = null;         outAttrs.inputType = InputType.TYPE_CLASS_TEXT;         outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;         return fic;     }      @Override     public boolean onCheckIsTextEditor() {         Log.d(TAG, "onCheckIsTextEditor");         return true;     }      @Override     public void onDraw(Canvas canvas) {         super.onDraw(canvas);          canvas.drawColor(R.color.grid_bg);         // .         // .         // alot more drawing code...         // .     } } 

The keyboard shows, but my onKeyListener only fires when I press the 'Next' button on the keyboard. I need which character is tapped, so that I can display it in my onDraw() method.

like image 657
rich.e Avatar asked Mar 24 '11 13:03

rich.e


People also ask

How do I show soft keyboard when Edittext is focused?

android:windowSoftInputMode="stateAlwaysVisible" -> in manifest File. edittext. requestFocus(); -> in code. This will open soft keyboard on which edit-text has request focus as activity appears.

What is soft keyboard in Android?

The soft keyboard (also called the onscreen keyboard) is the main input method on Android devices, and almost every Android developer needs to work with this component at some point.


2 Answers

It is actually possible to handle the key events yourself without deriving your view from TextView.

To do this, just modify your original code as follows:

1) Replace the following line in onCreateInputConnection():

outAttrs.inputType = InputType.TYPE_CLASS_TEXT; 

with this one:

outAttrs.inputType = InputType.TYPE_NULL; 

Per the documentation for InputType.TYPE_NULL: "This should be interpreted to mean that the target input connection is not rich, it can not process and show things like candidate text nor retrieve the current text, so the input method will need to run in a limited 'generate key events' mode."

2) Replace the following line in the same method:

BaseInputConnection fic = new BaseInputConnection(this, true); 

with this one:

BaseInputConnection fic = new BaseInputConnection(this, false); 

The false second argument puts the BaseInputConnection into "dummy" mode, which is also required in order for the raw key events to be sent to your view. In the BaseInputConnection code, you can find several comments such as the following: "only if dummy mode, a key event is sent for the new text and the current editable buffer cleared."

I have used this approach to have the soft keyboard send raw events to a view of mine that is derived from LinearLayout (i.e., a view not derived from TextView), and can confirm that it works.

Of course, if you didn't need to set the IME_ACTION_DONE imeOptions value to show a Done button on the keyboard, then you could just remove the onCreateInputConnection() and onCheckIsTextEditor() overrides entirely, and raw events would then be sent to your view by default, since no input connection capable of more sophisticated processing would have been defined.

But unfortunately, there does not seem to be a simple way to configure the EditorInfo attributes without overriding these methods and providing a BaseInputConnection object, and once you have done that you will have to dumb down the processing performed by that object as described above if you want to once again receive the raw key events.

WARNING: Two bugs were introduced in certain recent versions of the default LatinIME keyboard that ships with Android (Google Keyboard) that can impact keyboard event processing (as described above) when that keyboard is in use. I've devised some workarounds on the app side, with sample code, that appear to get around these problems. To view these workarounds, see the following answer:

Android - cannot capture backspace/delete press in soft. keyboard

like image 139
Carl Avatar answered Oct 07 '22 18:10

Carl


According to the documentation, a View (editor) receives commands from the Keyboard (IME) through an InputConnection and sends commands to the Keyboard through an InputMethodManager.

enter image description here

I will show the entire code below, but here are the steps.

1. Make the Keyboard to appear

Since the view is sending a command to the keyboard it needs to use an InputMethodManager. For the sake of the example, we will say that when the view is tapped it will show the keyboard (or hide it if it is already showing).

@Override public boolean onTouchEvent(MotionEvent event) {     if (event.getAction() == MotionEvent.ACTION_UP) {         InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);         imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);     }     return true; } 

The view also needs to have had setFocusableInTouchMode(true) set previously.

2. Receive input from the keyboard

In order for the view to receive input from the keyboard, it needs to override onCreateInputConnection(). This returns the InputConnection that the Keyboard uses to communicate with the view.

@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {     outAttrs.inputType = InputType.TYPE_CLASS_TEXT;     return new MyInputConnection(this, true); } 

The outAttrs specify what kind of keyboard the view is requesting. Here we are just requesting a normal text keyboard. Choosing TYPE_CLASS_NUMBER would display a number pad (if available). There are lots of other options. See EditorInfo.

You must return an InputConnection, which is usually a custom subclass of BaseInputConnection. In that subclass you provide a reference to your editable string, which the keyboard will make updates to. Since a SpannableStringBuilder implements the Editable interface, we will use that in our basic example.

public class MyInputConnection extends BaseInputConnection {      private SpannableStringBuilder mEditable;      MyInputConnection(View targetView, boolean fullEditor) {         super(targetView, fullEditor);         MyCustomView customView = (MyCustomView) targetView;         mEditable = customView.mText;     }      @Override     public Editable getEditable() {         return mEditable;     } } 

All we did here was provide the input connection with a reference to the text variable in our custom view. The BaseInputConnection will take care of editing that mText. This could very well be all that you need to do. However, you can check out the source code and see which methods say "default implementation", especially "default implementation does nothing." These are other methods you may want to override depending on how involved your editor view is going to be. You should also look through all the method names in the documentation. A number of them have notes to "editor authors". Pay special attention to those.

Some keyboards don't send certain input through the InputConnection for some reason (for example delete, enter, and some number pad keys). For those I added an OnKeyListener. Testing this setup on five different soft keyboards, everything seemed to work. Supplemental answers related to this are here:

  • Differentiating text keycode from control keycode in Android KeyEvent
  • Need table of key codes for android and presenter
  • Input connection for numeric type keyboard

Full project code

Here is my full example for reference.

enter image description here

MyCustomView.java

public class MyCustomView extends View {      SpannableStringBuilder mText;      public MyCustomView(Context context) {         this(context, null, 0);     }      public MyCustomView(Context context, AttributeSet attrs) {         this(context, attrs, 0);     }      public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         init();     }      private void init() {         setFocusableInTouchMode(true);         mText = new SpannableStringBuilder();          // handle key presses not handled by the InputConnection         setOnKeyListener(new OnKeyListener() {             public boolean onKey(View v, int keyCode, KeyEvent event) {                 if (event.getAction() == KeyEvent.ACTION_DOWN) {                      if (event.getUnicodeChar() == 0) { // control character                          if (keyCode == KeyEvent.KEYCODE_DEL) {                             mText.delete(mText.length() - 1, mText.length());                             Log.i("TAG", "text: " + mText + " (keycode)");                             return true;                         }                         // TODO handle any other control keys here                     } else { // text character                         mText.append((char)event.getUnicodeChar());                         Log.i("TAG", "text: " + mText + " (keycode)");                         return true;                     }                 }                 return false;             }         });     }      // toggle whether the keyboard is showing when the view is clicked     @Override     public boolean onTouchEvent(MotionEvent event) {         if (event.getAction() == MotionEvent.ACTION_UP) {             InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);             imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);         }         return true;     }      @Override     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {         outAttrs.inputType = InputType.TYPE_CLASS_TEXT;         // outAttrs.inputType = InputType.TYPE_CLASS_NUMBER; // alternate (show number pad rather than text)         return new MyInputConnection(this, true);     } } 

MyInputConnection.java

public class MyInputConnection extends BaseInputConnection {      private SpannableStringBuilder mEditable;      MyInputConnection(View targetView, boolean fullEditor) {         super(targetView, fullEditor);         MyCustomView customView = (MyCustomView) targetView;         mEditable = customView.mText;     }      @Override     public Editable getEditable() {         return mEditable;     }      // just adding this to show that text is being committed.     @Override     public boolean commitText(CharSequence text, int newCursorPosition) {         boolean returnValue = super.commitText(text, newCursorPosition);         Log.i("TAG", "text: " + mEditable);         return returnValue;     } } 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     tools:context="com.example.editorview.MainActivity">      <com.example.editorview.MyCustomView         android:id="@+id/myCustomView"         android:background="@android:color/holo_blue_bright"         android:layout_margin="50dp"         android:layout_width="300dp"         android:layout_height="150dp"         android:layout_centerHorizontal="true"         />  </RelativeLayout> 

There is nothing special in the MainActivity.java code.

Please leave a comment if this doesn't work for you. I am using this basic solution for a custom EditText in a library I am making and if there are any edge cases in which it doesn't work, I want to know. If you would like to view that project, the custom view is here. It's InputConnection is here.

Related

  • How to make a custom system keyboard
  • How to make a custom in-app keyboard
like image 25
Suragch Avatar answered Oct 07 '22 18:10

Suragch