android - 如何在输入法服务和服务内部的编辑文本之间建立一个inputConnection

标签 android android-edittext ime

我正在编写一个 IME (InputMethodService) 并且我在 IME 本身中有一个编辑文本,但是当我想在编辑文本中键入文本时,编辑文本无法获得焦点,我键入的只是转到另一个在 IME 之外编辑文本。如何使 IME 中的编辑文本像普通编辑文本一样工作

最佳答案

我解决这个问题的方法是保留两个输入连接,一个连接到用户正在编辑的原始文本字段,另一个连接到键盘内的文本字段。稍后更多关于第二个。您应该已经拥有的第一个,它是键盘发送所有命令的地方。在我的例子中,它被称为 mIC。因此,我在具有 InputConnection 的类上创建了另一个名为 mOtherIC 的 InputConnection 变量,并引入了一个名为 getIC() 的新方法,该方法仅返回当时要使用的这两个 InputConnections 中适当的一个(在我的例子中由 bool 值控制, 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;
    }
}

该类看起来很糟糕,因为我注释掉了无法构建的代码,大概是因为它访问了内部 Android API。也就是说,有一些注意事项(切换到此输入法时可能需要手动设置光标,两个 InputConnections 之间的大写形式结转),它可以工作。您现在所要做的就是在某个时候将我第一段中的 mOtherIC 设置为新的 CustomInputConnection(yourTextfield),其中 yourTextfield 是键盘内的文本字段。

关于android - 如何在输入法服务和服务内部的编辑文本之间建立一个inputConnection,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34468561/

相关文章:

android - 使用 Firebase 和 android 处理大于 Long 的整数

android - 是否可以在 GLSurfaceView 中绘制 View 或 ViewGroup

android - 限制 editText 接受字母(在 java 中)?

Android EditText MultiLine 与句子大写

html - 在谷歌浏览器中禁用 ime 模式

android - 即使打开也未找到 Flutter hive 盒

java - Android studio - 不同的设备使用相同的布局,但一切仅在其中一种设备上看起来不错

java - Android 无法在对话框中获取 EditText getText().toString()

android - setError(R.string.bla) 不起作用?

java - 从 SpellCheckerService 将单词添加到自定义词典中