как построить inputConnection между текстом редактирования, который находится внутри службы метода ввода и службы

Я пишу IME (InputMethodService), и у меня есть текст редактирования, который находится в самом IME, но когда я хочу ввести текст в текст редактирования, текст редактирования не может быть сфокусирован, и то, что я печатаю, просто переходит к другому тексту редактирования снаружи ИМЕ. как я могу заставить текст редактирования внутри IME работать как обычный текст редактирования


person DSapphire    schedule 26.12.2015    source источник


Ответы (1)


Я решил эту проблему, сохранив два входных соединения: одно к исходному текстовому полю, которое редактировал пользователь, а другое к текстовому полю внутри клавиатуры. О втором позже. Первый, который у вас уже должен быть, это место, куда клавиатура отправляет все свои команды. В моем случае это называлось mIC. Поэтому я создал еще одну переменную InputConnection с именем mOtherIC в моем классе, который имел InputConnection, и представил новый метод с именем getIC(), который просто возвращал соответствующий один из этих двух InputConnection, которые будут использоваться в то время (управляемый логическим значением в моем случае, true означало использование текстового поля на клавиатуре, false означало использование текстового поля, которое первоначально представляло клавиатуру). Каждый раз, когда я ранее обращался к mIC, я заменял его на getIC(), чтобы он мог беспрепятственно отправлять входные данные на правильное входное соединение.

InputConnection для текстового поля на клавиатуре здесь интересен, мне по сути пришлось скопировать внутренний Android EditableInputConnection в мой собственный класс CustomInputConnection:

public class CustomInputConnection extends BaseInputConnection {
    private static final boolean DEBUG = false;
    private static final String TAG = "CustomInputConnection";

    private final TextView mTextView;

    // Keeps track of nested begin/end batch edit to ensure this connection always has a
    // balanced impact on its associated TextView.
    // A negative value means that this connection has been finished by the InputMethodManager.
    private int mBatchEditNesting;

    public CustomInputConnection(TextView textview) {
        super(textview, true);
        mTextView = textview;
    }

    @Override
    public Editable getEditable() {
        TextView tv = mTextView;
        if (tv != null) {
            return tv.getEditableText();
        }
        return null;
    }

    @Override
    public boolean beginBatchEdit() {
        synchronized(this) {
            if (mBatchEditNesting >= 0) {
                mTextView.beginBatchEdit();
                mBatchEditNesting++;
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean endBatchEdit() {
        synchronized(this) {
            if (mBatchEditNesting > 0) {
                // When the connection is reset by the InputMethodManager and reportFinish
                // is called, some endBatchEdit calls may still be asynchronously received from the
                // IME. Do not take these into account, thus ensuring that this IC's final
                // contribution to mTextView's nested batch edit count is zero.
                mTextView.endBatchEdit();
                mBatchEditNesting--;
                return true;
            }
        }
        return false;
    }

//    @Override
//    protected void reportFinish() {
//        super.reportFinish();
//
//        synchronized(this) {
//            while (mBatchEditNesting > 0) {
//                endBatchEdit();
//            }
//            // Will prevent any further calls to begin or endBatchEdit
//            mBatchEditNesting = -1;
//        }
//    }

    @Override
    public boolean clearMetaKeyStates(int states) {
        final Editable content = getEditable();
        if (content == null) return false;
        KeyListener kl = mTextView.getKeyListener();
        if (kl != null) {
            try {
                kl.clearMetaKeyState(mTextView, content, states);
            } catch (AbstractMethodError e) {
                // This is an old listener that doesn't implement the
                // new method.
            }
        }
        return true;
    }

    @Override
    public boolean commitCompletion(CompletionInfo text) {
        if (DEBUG) Log.v(TAG, "commitCompletion " + text);
        mTextView.beginBatchEdit();
        mTextView.onCommitCompletion(text);
        mTextView.endBatchEdit();
        return true;
    }

    /**
     * Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
     */
    @Override
    public boolean commitCorrection(CorrectionInfo correctionInfo) {
        if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
        mTextView.beginBatchEdit();
        mTextView.onCommitCorrection(correctionInfo);
        mTextView.endBatchEdit();
        return true;
    }

    @Override
    public boolean performEditorAction(int actionCode) {
        if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
        mTextView.onEditorAction(actionCode);
        return true;
    }

    @Override
    public boolean performContextMenuAction(int id) {
        if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
        mTextView.beginBatchEdit();
        mTextView.onTextContextMenuItem(id);
        mTextView.endBatchEdit();
        return true;
    }

    @Override
    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
        if (mTextView != null) {
            ExtractedText et = new ExtractedText();
            if (mTextView.extractText(request, et)) {
                if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
//                    mTextView.setExtracting(request);
                }
                return et;
            }
        }
        return null;
    }

    @Override
    public boolean performPrivateCommand(String action, Bundle data) {
        mTextView.onPrivateIMECommand(action, data);
        return true;
    }

    @Override
    public boolean commitText(CharSequence text, int newCursorPosition) {
        if (mTextView == null) {
            return super.commitText(text, newCursorPosition);
        }
        if (text instanceof Spanned) {
            Spanned spanned = ((Spanned) text);
            SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
//            mIMM.registerSuggestionSpansForNotification(spans);
        }

//        mTextView.resetErrorChangedFlag();
        boolean success = super.commitText(text, newCursorPosition);
//        mTextView.hideErrorIfUnchanged();

        return success;
    }

    @Override
    public boolean requestCursorUpdates(int cursorUpdateMode) {
        if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);

        // It is possible that any other bit is used as a valid flag in a future release.
        // We should reject the entire request in such a case.
        final int KNOWN_FLAGS_MASK = InputConnection.CURSOR_UPDATE_IMMEDIATE |
                InputConnection.CURSOR_UPDATE_MONITOR;
        final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK;
        if (unknownFlags != 0) {
            if (DEBUG) {
                Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." +
                        " cursorUpdateMode=" + cursorUpdateMode +
                        " unknownFlags=" + unknownFlags);
            }
            return false;
        }

        return false;

//        if (mIMM == null) {
//            // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
//            // TODO: Return some notification code rather than false to indicate method that
//            // CursorAnchorInfo is temporarily unavailable.
//            return false;
//        }
//        mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
//        if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
//            if (mTextView == null) {
//                // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
//                // TODO: Return some notification code for the input method that indicates
//                // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
//            } else if (mTextView.isInLayout()) {
//                // In this case, the view hierarchy is currently undergoing a layout pass.
//                // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
//                // pass is finished.
//            } else {
//                // This will schedule a layout pass of the view tree, and the layout event
//                // eventually triggers IMM#updateCursorAnchorInfo.
//                mTextView.requestLayout();
//            }
//        }
//        return true;
    }
}

Класс выглядит довольно плохо, потому что я закомментировал код, который не собирался, по-видимому, из-за того, что он обращается к внутренним API Android. Тем не менее, с некоторыми оговорками (курсор, вероятно, нужно установить вручную, когда вы переключаетесь на этот метод ввода, заглавные буквы переносятся между двумя InputConnections), это работает. Все, что вам нужно сделать сейчас, это в какой-то момент установить mOtherIC из моего первого абзаца в новое CustomInputConnection(yourTextfield), где yourTextfield — это текстовое поле внутри клавиатуры.

person Dennis L    schedule 12.09.2016