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

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

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

Вот мой 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... // . } } 

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

Фактически можно обрабатывать ключевые события самостоятельно, не получая свое представление из 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» в мягкой форме. клавиатура

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

Еще одна вещь, для тех, кто попробует эту технику; TextView не может редактировать текст по умолчанию, поэтому, если вы хотите сделать свою реализацию редактируемой (вместо подclassификации 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 подclassом BaseInputConnection . В этом подclassе вы предоставляете ссылку на свою редактируемую строку, на которую клавиатура будет обновлять. Поскольку 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

     

В коде 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() )

  • Разница между make и build в Android Studio
  • Как получить доступ к файлу в папке с ресурсами?
  • Как обращаться с ListView на Android
  • Чтение файла sqlite из папки с данными
  • Java.lang.ClassNotFoundException после обновления студии Android
  • Android-студия Gradle наращивает скорость
  • FragmentPagerAdapter getItem не вызывается
  • Как обновить TextView от другого classа
  • Firebase Cloud Messaging разные ключи
  • Включение WiFi в эмулятор Android
  • В чем разница между начальным / конечным маржем Android и правым / левым?
  • Давайте будем гением компьютера.