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

У меня есть подклассовый вид, который появляется на клавиатуре, когда он получает "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().

Ответ 1

Оказывается, что я действительно нуждался в подклассе 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;
 }

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

Ответ 2

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

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

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

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

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

Android - не удается захватить backspace/delete, нажмите soft. Клавиатура

Ответ 3

Согласно документации, View (редактор) получает команды от клавиатуры (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 отображает цифровую панель (если она доступна). Есть много других вариантов. См. EditorInfo.

Вы должны вернуть InputConnection, который обычно является обычным подклассом 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. Испытывая эту настройку на пяти различных клавиатурах, все, казалось, сработало. Дополнительные ответы, связанные с этим, приведены здесь:

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

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

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

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 здесь.

Похожие

Ответ 4

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

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

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