返回介绍

6. TextView 接收软键盘输入

发布于 2024-12-23 22:05:51 字数 5523 浏览 0 评论 0 收藏 0

Android 上的标准文本编辑控件是 EditText,而 EditText 对软键盘输入的处理,却是在 TextView 内部实现的。Android 为所有的 View 预留了一个接收软键盘输入的接口类,叫 InputConnection。软键盘以 InputConnection 为桥梁把文字输入、文字修改、文字删除等传递给 View。任意 View 只要重写 onCheckIsTextEditor() 并返回 true,然后重写 onCreateInputConnection(EditorInfo outAttrs) 返回一个 InputConnection 的实例,便可以接收软键盘的输入。TextView 的软键盘输入接收,是通过 EditableInputConnection 类来实现的。

public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    //判断是否处于可编辑状态
    if (onCheckIsTextEditor() && isEnabled()) {
      mEditor.createInputMethodStateIfNeeded();
      
      //设置输入法相关的信息
      outAttrs.inputType = getInputType();
      if (mEditor.mInputContentType != null) {
        outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
        outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
        outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
        outAttrs.actionId = mEditor.mInputContentType.imeActionId;
        outAttrs.extras = mEditor.mInputContentType.extras;
      } else {
        outAttrs.imeOptions = EditorInfo.IME_NULL;
      }
      if (focusSearch(FOCUS_DOWN) != null) {
        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
      }
      if (focusSearch(FOCUS_UP) != null) {
        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
      }
      if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
          == EditorInfo.IME_ACTION_UNSPECIFIED) {
        
        if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
          //把软键盘的 enter 设为下一步
          outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
        } else {
          //把软键盘的 enter 设为完成
          outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
        }
        if (!shouldAdvanceFocusOnEnter()) {
          outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
        }
      }
      if (isMultilineInputType(outAttrs.inputType)) {
        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
      }
      outAttrs.hintText = mHint;
      
      //判断 TextView 内部文本是否可编辑
      if (mText instanceof Editable) {
        //返回 EditableInputConnection 实例
        InputConnection ic = new EditableInputConnection(this);
        outAttrs.initialSelStart = getSelectionStart();
        outAttrs.initialSelEnd = getSelectionEnd();
        outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
        return ic;
      }
    }
    return null;
  }

我们再来看一下 EditableInputConnection 里面的几个主要的方法:

首先是 commitText 方法,这个方法接收输入法输入的字符并提交给 TextView。

public boolean commitText(CharSequence text, int newCursorPosition) {
    //判断 TextView 是否为空
    if (mTextView == null) {
      return super.commitText(text, newCursorPosition);
    }
    //判断文本是否 Span,来自输入法的 Span 一般只有 SuggestionSpan,SuggestionSpan 携带了输入法的错别字修正的词
    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;
  }

getEditable 方法,这个方法并不是 InputConnection 接口的一部分,而是 EditableInputConnection 的父类 BaseInputConnection 的方法,用来获取一个可编辑对象,EditableInputConnection 里面的所有修改都针对这个可编辑对象来做。

public Editable getEditable() {
    TextView tv = mTextView;
    if (tv != null) {
      //返回 TextView 的可编辑对象
      return tv.getEditableText();
    }
    return null;
  }

deleteSurroundingText 方法,这个方法用来删除光标前后的内容:

public boolean deleteSurroundingText(int beforeLength, int afterLength) {
    if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
        + " / " + afterLength);
    final Editable content = getEditable();
    if (content == null) return false;

    //批量删除标记
    beginBatchEdit();
    
    //获取当前已选择的文本的位置
    int a = Selection.getSelectionStart(content);
    int b = Selection.getSelectionEnd(content);

    if (a > b) {
      int tmp = a;
      a = b;
      b = tmp;
    }

    int ca = getComposingSpanStart(content);
    int cb = getComposingSpanEnd(content);
    if (cb < ca) {
      int tmp = ca;
      ca = cb;
      cb = tmp;
    }
    if (ca != -1 && cb != -1) {
      if (ca < a) a = ca;
      if (cb > b) b = cb;
    }

    int deleted = 0;

    //删除光标之前的文本
    if (beforeLength > 0) {
      int start = a - beforeLength;
      if (start < 0) start = 0;
      content.delete(start, a);
      deleted = a - start;
    }
    //删除光标之后的文本
    if (afterLength > 0) {
      b = b - deleted;

      int end = b + afterLength;
      if (end > content.length()) end = content.length();

      content.delete(b, end);
    }
    
    //结束批量编辑
    endBatchEdit();
    
    return true;
  }

commitCompletion 和 commitCorrection 方法,即是用来补全单词和修正错别字的方法,这两个方法内部都是调用 TextView 对应的方法来实现的。

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

  @Override
  public boolean commitCorrection(CorrectionInfo correctionInfo) {
    if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
    mTextView.beginBatchEdit();
    mTextView.onCommitCorrection(correctionInfo);
    mTextView.endBatchEdit();
    return true;
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文