Android输入法,如何在EditText中设置光标位置?

14
我正在开发软键盘,在其中需要在IME编辑文本中设置光标位置。
如上图所示,我已创建了软键盘,我们可以看到一些文本已输入到编辑文本中,并显示当前光标位置(用蓝色指示器表示)。
我需要将光标位置设置在当前行的末尾(在我们的案例中,即在图像中红色显示的第一行的末尾)。
我已尝试使用提供的不同函数InputConnection,我尝试过:
CharSequence seq = conn.getTextBeforeCursor(1000, 0);     // will get as much characters as possible on the left of cursor

还有一件事,我还需要计算编辑文本中的行数(在我们的情况下是两行)。

4个回答

11

其他一些回答似乎过于复杂或不完整。这是一个面向未来访问者的通用答案。

在一个活动中

如果您有EditText的引用,那么很容易。

设置光标位置

editText.setSelection(index);

设定所选范围

editText.setSelection(startIndex, endIndex);

在IME(键盘)中

由于您无法直接访问EditText,因此从IME中操作会更加困难。但是,您可以使用InputConnection来设置光标位置和选择。

以下答案展示如何从您的InputMethodService子类中获取输入连接:

InputConnection inputConnection = getCurrentInputConnection();

设置光标位置

inputConnection.setSelection(index, index);

设置选定的范围

inputConnection.setSelection(startIndex, endIndex);

将光标移动到开头

inputConnection.setSelection(0, 0);

将光标移动到末尾

ExtractedText extractedText = inputConnection.getExtractedText(new ExtractedTextRequest(), 0);
if (extractedText == null || extractedText.text == null) return;
int index = extractedText.text.length();
inputConnection.setSelection(index, index);

这种方法不能保证一定有效,因为如果EditText中的文本非常长,则提取出的文本不会是整个文本。不过,在大多数情况下,这种方法还是可以的。另一个选择是使用以下组合:

  • inputConnection.getTextBeforeCursor(numberOfChars, 0)
  • inputConnection.getSelectedText(0)
  • inputConnection.getTextAfterCursor(numberOfChars, 0)

其中numberOfChars是一个较大的数字。

获取光标位置(或所选内容)

ExtractedText extractedText = inputConnection.getExtractedText(new ExtractedTextRequest(), 0);
int startIndex = extractedText.startOffset + extractedText.selectionStart;
int endIndex = extractedText.startOffset +  extractedText.selectionEnd;

extractedText不能返回EditText的全部文本的情况下,startOffset会告诉你从哪个点开始提取的。然后你可以通过将startOffset添加到提取文本的选择开头或结尾来获得实际的光标索引。

相对于当前位置移动光标

一旦你知道了光标的当前位置,就可以很容易地移动它。下面是将光标移到上一个单词的开头的示例。

BreakIterator boundary = BreakIterator.getWordInstance();
boundary.setText(extractedText.text.toString());
int preceding = boundary.preceding(extractedText.selectionStart);
int newIndex = (preceding == BreakIterator.DONE) ? selectionStart : preceding;
inputConnection.setSelection(newIndex, newIndex);

可以查看其他BreakIterator选项,请在此处查看。

您还可以通过发送d-pad向下和向上事件来左右、上下移动。

向左移动

inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT));
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_LEFT));

向右移动

inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT));
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_RIGHT));

向上移动

inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP));
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_UP));

向下移动

inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN));

你救了我的一天(们)。很难相信这个答案只有一个赞,因为它是操纵光标位置的正确方法。无论如何,谢谢。 - CyberMJ

2

大家好,感谢您的赞赏。两天前我找到了解决方案,但无法更新我的答案。

为此,我使用了下面的代码:

如果我们使用的API版本大于10,则可以使用以下代码:

sendDownUpKeyEvents(0x0000007b);

因为这个方法是在api 11中添加的。

如果我们使用的api版本小于11,

if (getCurrentInputConnection() != null) {
                    CharSequence textAfter = getCurrentInputConnection().getTextAfterCursor(1024, 0);
                    if (!TextUtils.isEmpty(textAfter)) {
                        int newPosition = 1;
                        while (newPosition < textAfter.length()) {
                            char chatAt = textAfter.charAt(newPosition);
                            if (chatAt == '\n' || chatAt == '\r') {
                                break;
                            }
                            newPosition++;
                        }
                        if (newPosition > textAfter.length())
                            newPosition = textAfter.length();
                        try {
                            CharSequence textBefore = getCurrentInputConnection().getTextBeforeCursor(Integer.MAX_VALUE, 0);
                            if (!TextUtils.isEmpty(textBefore)) {
                                newPosition = newPosition + textBefore.length();
                            }
                            getCurrentInputConnection().setSelection(newPosition, newPosition);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }

太好了,实际上我理解你试图考虑自动换行的行,而不是以 '\n' 或 '\r' 结尾的行,这些是用户输入的真正换行符。无论如何,很高兴你找到了解决方案! - Carlos Robles
这似乎是过于繁琐的解析,只为设置光标位置。 - user9599745

1
以下是一些可行的代码,可以实现您想要的功能。
非常简单:
要将光标位置设置为第一行的末尾,我们首先必须获取该行最后一个文本字符的索引。 EditText的布局上有一个方法可以做到这一点。
布局是编辑文本内部用于布局文本的内容,它有一个称为getLineEnd的方法,该方法返回行末的文本索引(我们需要从中减去1,否则我们的光标会在下一行的开头结束)。
一旦我们有了要设置光标的位置,我们只需使用EditText的setSelection方法即可。
int endOfFirstLine = editText.getLayout().getLineEnd(0)-1;
//set the text selection (cursor postion) to that index
editText.setSelection(endOfFirstLine);

我们也可以很容易地获取行数,只需要使用以下代码:
int lineCount = editText.getLineCount();

这段代码的诀窍在于它必须在EditText的布局构建完成后运行。这意味着如果我们只是将其放在onCreate或onResume中,它将无法工作。行数将为0。
因此,我们必须在布局过程完成后运行此代码。我们通过向edittext添加布局监听器并在其完成后运行代码来实现这一点。
editText.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override public void onGlobalLayout() {
}

这是完整的活动代码:

public class MainActivity extends Activity {

    private EditText editText;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = (EditText) findViewById(R.id.test_edittext);

        editText.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override public void onGlobalLayout() {

                //this gives us the index of the text at the end of the first line.
                int endOfFirstLine = editText.getLayout().getLineEnd(0)-1;
                //set the text selection (cursor postion) to that index
                editText.setSelection(endOfFirstLine);

                //we can get the line count with getLineCount
                int lineCount = editText.getLineCount();

                Toast.makeText(MainActivity.this, "number of edittext lines: " + lineCount, Toast.LENGTH_LONG).show();
            }
        });

    }

    @Override protected void onResume() {
        super.onResume();



    }

}

1
很抱歉,我要告诉你无法从IME完成此操作。为此,您应该访问EditText的某些方法,但没有任何可用的API,因为textview控件在另一个进程的活动中,而不是您的进程。您可以获得的所有信息都是EditorInfo参数中的信息,您可以在一些InputMethodService方法中看到,例如onStartInput(EditorInfo attribute, boolean restarting)。可能在将来会包括其他访问更多信息的方式,但目前为止,您所能做的就是使用InputConnection的setSelection方法更改光标位置,但您也不知道第一行末尾的位置,因此这对您需要的内容并不真正有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接