package com.yuruiyin.richeditor; import android.content.Context; import android.text.Editable; import android.text.ParcelableSpan; import com.hanks.lineheightedittext.TextWatcher; import com.yuruiyin.richeditor.config.AppConfig; import com.yuruiyin.richeditor.span.BlockImageSpan; /** * Title: 编辑器字符变化监听器 * Description: 如在段落图片后面输入字符时自动换行等。 * * @author yuruiyin * @version 2019-04-29 */ public class RichTextWatcher implements TextWatcher { private Context mContext; private RichEditText mEditText; // 在修改前的输入框的文本长度 private int beforeEditContentLen = 0; // 需要插入回车符的位置,场景:在imageSpan后面输入文字时候需要换行 private int needInsertBreakLinePosAfterImage = -1; // 是否需要在ImageSpan之前插入换行,场景:在imageSpan前面输入文字时候需要换行 private boolean isNeedInsertBreakLineBeforeImage; // 上次的输入框内容 private String lastEditTextContent = ""; // 是否删除了回车符 private boolean isDeleteEnterStr; public RichTextWatcher(RichEditText editText) { mEditText = editText; mContext = mEditText.getContext(); } /** * 删除字符的时候,要删除当前位置start和end相等的span */ private void handleDelete() { Editable editable = mEditText.getEditableText(); int cursorPos = mEditText.getSelectionStart(); ParcelableSpan[] parcelableSpans = editable.getSpans(cursorPos, cursorPos, ParcelableSpan.class); for (ParcelableSpan span : parcelableSpans) { if (editable.getSpanStart(span) == editable.getSpanEnd(span)) { editable.removeSpan(span); } } if (isDeleteEnterStr) { // 删除了回车符,如果回车前后两行只要有一行是block样式,就要合并 mEditText.getRichUtils().mergeBlockSpanAfterDeleteEnter(); } } /** * 修改上一行的段样式或者上一行末尾的N个行内样式的flag为end exclusive,即当前行与上一行断开连接 */ private void changeLastBlockOrInlineSpanFlag() { mEditText.getRichUtils().changeLastBlockOrInlineSpanFlag(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { isDeleteEnterStr = after == 0 && s.length() > 0 && s.charAt(start) == '\n'; beforeEditContentLen = s.length(); Editable editable = mEditText.getText(); int curPos = mEditText.getSelectionStart(); // 判断是否在图片后输入 if (curPos == 0) { needInsertBreakLinePosAfterImage = -1; } else { // 判断是否在图片后面输入 BlockImageSpan[] blockImageSpansAfter = editable.getSpans(curPos - 1, curPos, BlockImageSpan.class); if (blockImageSpansAfter.length > 0) { //说明当前光标处于imageSpan的后面,如果在当前位置输入文字,需要另起一行 needInsertBreakLinePosAfterImage = curPos; } else { needInsertBreakLinePosAfterImage = -1; } } // 判断是否在图片前面输入 BlockImageSpan[] blockImageSpansBefore = editable.getSpans(curPos, curPos + 1, BlockImageSpan.class); // 说明当前光标在ImageSpan的前面 isNeedInsertBreakLineBeforeImage = blockImageSpansBefore.length > 0; } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } private boolean isInUndo(String preContent, String curContent) { String addContent = curContent.substring(preContent.length()); return addContent.equals("\n" + AppConfig.IMAGE_SPAN_PLACEHOLDER); } @Override public void afterTextChanged(Editable s) { if (s.toString().length() < beforeEditContentLen) { // 说明删除了字符 if (s.length() > 0) { handleDelete(); } lastEditTextContent = s.toString(); return; } int cursorPos = mEditText.getSelectionStart(); String editContent = s.toString(); // 如果是删除imageSpan,然后再执行undo的时候,不要在插入'\n',否则可能导致死循环 if (needInsertBreakLinePosAfterImage != -1 && cursorPos > 0 && editContent.charAt(cursorPos - 1) != '\n' && !isInUndo(lastEditTextContent, editContent)) { //在imageSpan后面输入了文字(除了'\n'),则需要换行 s.insert(needInsertBreakLinePosAfterImage, "\n"); } if (isNeedInsertBreakLineBeforeImage && cursorPos >= 0) { // 在ImageSpan前输入回车, 则需要将光标移动到上一个行 // 在ImageSpan前输入文字(除了'\n'),则需要先换行,在将光标移动到上一行 if (cursorPos > 0 && editContent.charAt(cursorPos - 1) != '\n') { s.insert(cursorPos, "\n"); } mEditText.setSelection(cursorPos); } if (cursorPos > 0 && editContent.charAt(cursorPos - 1) == '\n' && !editContent.equals(lastEditTextContent)) { // 输入了回车, 需要断开上一行的样式(包括inline和block) lastEditTextContent = s.toString(); changeLastBlockOrInlineSpanFlag(); } lastEditTextContent = s.toString(); } }