Как захватить ввод мягкой клавиатуры в режиме просмотра?

У меня есть подклассовый вид, который появляется на клавиатуре, когда он получает «touch up» в onTouchEvent. Он показывает это, запрашивая фокус, извлекая InputMethodManager, а затем вызывая showSoftInput.

Теперь мне нужно выяснить, как захватить нажатые буквы мягкой клавиатуры, когда они нажаты . В настоящее время я получаю только ответ, когда на мягкой клавиатуре нажата кнопка «Далее / Готово».

Вот мой класс:

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

Клавиатура показывает, но мой onKeyListener срабатывает только тогда, когда я нажимаю кнопку «Далее» на клавиатуре. Мне нужен, какой символ прослушивается, так что я могу отобразить его в моем методе onDraw ().

Solutions Collecting From Web of "Как захватить ввод мягкой клавиатуры в режиме просмотра?"

Фактически можно обрабатывать ключевые события самостоятельно, не получая ваш вид из TextView.

Для этого просто измените исходный код следующим образом:

1) Замените следующую строку в onCreateInputConnection():

 outAttrs.inputType = InputType.TYPE_CLASS_TEXT; 

с этим:

 outAttrs.inputType = InputType.TYPE_NULL; 

В документации для InputType.TYPE_NULL: «Это должно интерпретироваться как означающее, что целевое входное соединение не является богатым, оно не может обрабатывать и показывать такие вещи, как текст-кандидат, и не извлекать текущий текст, поэтому метод ввода должен выполняться в Ограниченный режим генерации ключевых событий ».

2) Замените следующую строку тем же методом:

 BaseInputConnection fic = new BaseInputConnection(this, true); 

с этим:

 BaseInputConnection fic = new BaseInputConnection(this, false); 

Аргумент false второй аргумент помещает BaseInputConnection в «фиктивный» режим, который также необходим для отправки сырых ключевых событий вашему представлению. В коде BaseInputConnection вы можете найти несколько комментариев, таких как следующее: «только в режиме фиктивного события для нового текста отправляется ключевое событие и очищается текущий очищаемый буфер».

Я использовал этот подход, чтобы мягкая клавиатура посылала необработанные события на мой взгляд, который получен из LinearLayout (т. Е. Представление, не полученное из TextView), и может подтвердить, что он работает.

Конечно, если вам не нужно было устанавливать значение IME_ACTION_DONE imeOptions для отображения кнопки «Готово» на клавиатуре, вы можете просто удалить onCreateInputConnection() и onCheckIsTextEditor() , а затем необработанные события будут отправлены на ваш просмотр По умолчанию, поскольку никакое входное соединение, способное к более сложной обработке, не было бы определено.

Но, к сожалению, не существует простого способа настройки атрибутов EditorInfo без переопределения этих методов и предоставления объекта BaseInputConnection, и как только вы это сделаете, вам придется окутать обработку, выполняемую этим объектом, как описано выше, если вы Хотите снова получить сырые ключевые события.

ПРЕДУПРЕЖДЕНИЕ. В некоторых последних версиях клавиатуры LatinIME по умолчанию были установлены две ошибки, которые поставляются с Android (Google Keyboard), которые могут влиять на обработку событий клавиатуры (как описано выше), когда эта клавиатура используется. Я разработал некоторые обходные пути на стороне приложения, с примером кода, которые, как представляется, обойти эти проблемы. Чтобы просмотреть эти обходные пути, см. Следующий ответ:

Android – не может захватывать нажатие клавиши «backspace / delete» в мягкой форме. клавиатура

Оказывается, что я действительно нуждался в подклассе TextView и использовании addTextChangedListener () для добавления моей собственной реализации TextWatcher для прослушивания событий софт-ключа. Я не мог найти способ сделать это с простым старым представлением.

Еще одна вещь, для тех, кто попробует эту технику; TextView не может редактировать текст по умолчанию, поэтому, если вы хотите, чтобы ваша реализация редактировалась (вместо подкласса EditText, который я не хотел делать), вы также должны создать пользовательский InputConnection, что-то вроде следующего:

  /** * MyInputConnection * BaseInputConnection configured to be editable */ class MyInputConnection extends BaseInputConnection { private SpannableStringBuilder _editable; TextView _textView; public MyInputConnection(View targetView, boolean fullEditor) { super(targetView, fullEditor); _textView = (TextView) targetView; } public Editable getEditable() { if (_editable == null) { _editable = (SpannableStringBuilder) Editable.Factory.getInstance() .newEditable("Placeholder"); } return _editable; } public boolean commitText(CharSequence text, int newCursorPosition) { _editable.append(text); _textView.setText(text); return true; } } 

Затем вы переопределяете onCheckisTextEditor и onCreateInputConnection с чем-то вроде следующего:

  @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.actionLabel = null; outAttrs.label = "Test text"; outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; return new MyInputConnection(this, true); } @Override public boolean onCheckIsTextEditor() { return true; } 

После этого у вас должен быть вид, который может прослушивать мягкую клавиатуру, и вы можете делать все, что хотите, с входными значениями клавиш.

Согласно документации , View (editor) получает команды от Keyboard (IME) через InputConnection и отправляет команды на клавиатуру через InputMethodManager .

Введите описание изображения здесь

Я покажу весь код ниже, но вот шаги.

1. Сделайте клавиатуру

Поскольку представление отправляет команду на клавиатуру, ему необходимо использовать InputMethodManager . Для примера, мы скажем, что при просмотре представления будет отображаться клавиатура (или скрыть ее, если она уже отображается).

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

Представление также должно быть установлено setFocusableInTouchMode(true) установленным ранее.

2. Получить ввод с клавиатуры

Для того, чтобы представление получало ввод с клавиатуры, ему необходимо переопределить onCreateInputConnection() . Это возвращает InputConnection который использует клавиатура для связи с представлением.

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

outAttrs указывают, какую клавиатуру запрашивает вид. Здесь мы просто запрашиваем обычную текстовую клавиатуру. При выборе TYPE_CLASS_NUMBER будет отображаться TYPE_CLASS_NUMBER панель (если она доступна). Есть много других вариантов. См. EditorInfo .

Вы должны вернуть InputConnection , который обычно является BaseInputConnection подклассом BaseInputConnection . В этом подклассе вы предоставляете ссылку на свою редактируемую строку, на которую клавиатура будет обновлять. Поскольку SpannableStringBuilder реализует Editable интерфейс, мы будем использовать его в нашем базовом примере.

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

Все, что мы здесь делали, это обеспечить входное соединение со ссылкой на текстовую переменную в нашем пользовательском представлении. BaseInputConnection позаботится о редактировании этого mText . Это может быть все, что вам нужно сделать. Однако вы можете проверить исходный код и посмотреть, какие методы говорят «реализация по умолчанию», особенно «реализация по умолчанию ничего не делает». Это другие методы, которые вы можете переопределить в зависимости от того, как будет выглядеть ваш редактор. Вы также должны просмотреть все имена методов в документации . Некоторые из них имеют заметки для «авторов-редакторов». Обратите особое внимание на них.

По некоторым причинам некоторые клавиатуры не посылают определенный вход через InputConnection (например, удалять , вводить и некоторые клавиши цифровой клавиатуры ). Для тех, кого я добавил, OnKeyListener . Испытывая эту настройку на пяти различных клавиатурах, все, казалось, сработало. Дополнительные ответы, связанные с этим, приведены здесь:

  • Дифференцирование текстового ключевого кода из управляющего кода в Android KeyEvent
  • Нужна таблица кодов клавиш для андроида и докладчика
  • Входное соединение для цифровой клавиатуры

Полный код проекта

Вот мой полный пример для справки.

Введите описание изображения здесь

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> 

В коде MainActivity.java нет ничего особенного.

Пожалуйста, оставьте комментарий, если это не сработает для вас. Я использую это базовое решение для пользовательского EditText в библиотеке, которую я создаю, и если есть какие-либо краевые случаи, в которых она не работает, я хочу знать. Если вы хотите просмотреть этот проект, пользовательский вид находится здесь . Это InputConnection здесь .

Связанный

  • Как создать пользовательскую системную клавиатуру
  • Как создать обычную клавиатуру в приложении

Мое понимание заключается в том, что ваш onKeyListener будет получать аппаратные ключевые события клавиатуры.

Вы получите доступ ко всем входным ключевым событиям, если вы переопределите boolean View.onKeyPreIme(int keyCode, KeyEvent event)

Таким образом, вы можете выбрать способ действия с ключевым событием [ DOWN | MULTIPLE | UP ] [ DOWN | MULTIPLE | UP ] [ DOWN | MULTIPLE | UP ] и вернуть true или разрешить обработку нормального ключа ( return super.onKeyPreIme() )