下面列出了android.text.Editable#getSpans ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
@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;
}
/**
* Perform redo.
*/
public void redo() {
EditItem edit = mEditHistory.getNext();
if (edit == null) {
return;
}
Editable text = mTextView.getEditableText();
int start = edit.mmStart;
int end = start + (edit.mmBefore != null ? edit.mmBefore.length() : 0);
mIsUndoOrRedo = true;
text.replace(start, end, edit.mmAfter);
mIsUndoOrRedo = false;
// This will get rid of underlines inserted when editor tries to come
// up with a suggestion.
for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) {
text.removeSpan(o);
}
Selection.setSelection(text, edit.mmAfter == null ? start
: (start + edit.mmAfter.length()));
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
if (autoCompleting)
return;
if (selStart == selEnd) {
Editable text = getText();
HintSpan[] spans = text.getSpans(0, length(), HintSpan.class);
if (spans.length > 1)
throw new IllegalStateException("more than one HintSpan");
autoCompleting = true;
if (spans.length == 1) {
HintSpan span = spans[0];
if (selStart >= text.getSpanStart(span) && selStart < text.getSpanEnd(span)) {
setSelection(text.getSpanStart(span));
} else if (selStart == text.getSpanEnd(span)) {
text.removeSpan(span);
super.setImeOptions(prevOptions);
}
}
}
autoComplete();
autoCompleting = false;
super.onSelectionChanged(selStart, selEnd);
}
/**
* Remove an object from the token list. Will remove duplicates if present or do nothing if no
* object is present in the view. Uses {@link Object#equals(Object)} to find objects. May only
* be called from the main thread
*
* @param object object to remove, may be null or not in the view
*/
@UiThread
public void removeObjectSync(T object) {
//To make sure all the appropriate callbacks happen, we just want to piggyback on the
//existing code that handles deleting spans when the text changes
ArrayList<Editable>texts = new ArrayList<>();
//If there is hidden content, it's important that we update it first
if (hiddenContent != null) {
texts.add(hiddenContent);
}
if (getText() != null) {
texts.add(getText());
}
// If the object is currently visible, remove it
for (Editable text: texts) {
TokenImageSpan[] spans = text.getSpans(0, text.length(), TokenImageSpan.class);
for (TokenImageSpan span : spans) {
if (span.getToken().equals(object)) {
removeSpan(text, span);
}
}
}
updateCountSpan();
}
/**
* Get the list of Tokens
*
* @return List of tokens
*/
public List<T> getObjects() {
ArrayList<T>objects = new ArrayList<>();
Editable text = getText();
if (hiddenContent != null) {
text = hiddenContent;
}
for (TokenImageSpan span: text.getSpans(0, text.length(), TokenImageSpan.class)) {
objects.add(span.getToken());
}
return objects;
}
/**
* highlight find word
*
* @param what - input
* @param regex - is java regex
* @param wordOnly - find word only
*/
public void find(String what, boolean regex, boolean wordOnly, boolean matchCase) {
Pattern pattern;
if (regex) {
if (matchCase) {
pattern = Pattern.compile(what);
} else {
pattern = Pattern.compile(what, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
} else {
if (wordOnly) {
if (matchCase) {
pattern = Pattern.compile("\\s" + what + "\\s");
} else {
pattern = Pattern.compile("\\s" + Pattern.quote(what) + "\\s", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
} else {
if (matchCase) {
pattern = Pattern.compile(Pattern.quote(what));
} else {
pattern = Pattern.compile(Pattern.quote(what), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
}
}
Editable e = getEditableText();
//remove all span
BackgroundColorSpan spans[] = e.getSpans(0, e.length(), BackgroundColorSpan.class);
for (int n = spans.length; n-- > 0; )
e.removeSpan(spans[n]);
//set span
for (Matcher m = pattern.matcher(e); m.find(); ) {
e.setSpan(new BackgroundColorSpan(codeTheme.getErrorColor()),
m.start(),
m.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private Range getCurrentCandidateTokenRange() {
Editable editable = getText();
int cursorEndPosition = getSelectionEnd();
int candidateStringStart = prefix.length();
int candidateStringEnd = editable.length();
if (hintVisible) {
//Don't try to search the hint for possible tokenizable strings
candidateStringEnd = candidateStringStart;
}
//We want to find the largest string that contains the selection end that is not already tokenized
TokenImageSpan[] spans = editable.getSpans(prefix.length(), editable.length(), TokenImageSpan.class);
for (TokenImageSpan span : spans) {
int spanEnd = editable.getSpanEnd(span);
if (candidateStringStart < spanEnd && cursorEndPosition >= spanEnd) {
candidateStringStart = spanEnd;
}
int spanStart = editable.getSpanStart(span);
if (candidateStringEnd > spanStart && cursorEndPosition <= spanEnd) {
candidateStringEnd = spanStart;
}
}
List<Range> tokenRanges = tokenizer.findTokenRanges(editable, candidateStringStart, candidateStringEnd);
for (Range range: tokenRanges) {
if (range.start <= cursorEndPosition && cursorEndPosition <= range.end) {
return range;
}
}
return new Range(cursorEndPosition, cursorEndPosition);
}
private void findClose(char open, int selEnd) {
Log.d(TAG, "findClose() called with: open = [" + open + "], selEnd = [" + selEnd + "]");
Editable text = editText.getText();
int cursor = selEnd + 1;
int count = 1;
char close = getClose(open);
boolean find = false;
while (cursor < text.length()) {
char chatAtCursor = text.charAt(cursor);
if (chatAtCursor == open) {
count++;
} else if (chatAtCursor == close) {
count--;
}
if (count == 0) {
find = true;
break;
}
cursor++;
}
BracketSpan[] spans = text.getSpans(0, text.length(), BracketSpan.class);
for (BracketSpan span : spans) {
text.removeSpan(span);
}
text.setSpan(new BracketSpan(codeTheme.getBracketColor(),
codeTheme.getTextColor()), selEnd, selEnd + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (find) {
text.setSpan(new BracketSpan(codeTheme.getBracketColor(),
codeTheme.getTextColor()), cursor, cursor + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void removeSpan(TokenImageSpan span) {
Editable text = getText();
if (text == null) return;
//If the spanwatcher has been removed, we need to also manually trigger onSpanRemoved
TokenSpanWatcher[] spans = text.getSpans(0, text.length(), TokenSpanWatcher.class);
if (spans.length == 0) {
spanWatcher.onSpanRemoved(text, span, text.getSpanStart(span), text.getSpanEnd(span));
}
//Add 1 to the end because we put a " " at the end of the spans when adding them
text.delete(text.getSpanStart(span), text.getSpanEnd(span) + 1);
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if ("ul".equalsIgnoreCase(tag)) {
// Ensure there are at least two newlines both before and after the list.
ensureAtLeastTwoTrailingNewlines(output);
} else if ("li".equalsIgnoreCase(tag)) {
appendNewlineIfNoTrailingNewline(output);
int outputLength = output.length();
if (opening) {
// Attach a BulletSpan to the beginning of the list entry. The span will be removed
// when processing the closing of this tag/entry.
output.setSpan(new BulletSpan(), outputLength, outputLength, Spannable.SPAN_MARK_MARK);
} else {
// Attach a BulletSpan, spanning the whole list entry. This removes the span
// attached to the start of this list entry when processing the opening of this tag/entry.
// We also attach a LeadingMarginSpan to the same location to indent the list entries
// and their bullets.
BulletSpan[] bulletSpans = output.getSpans(0, outputLength, BulletSpan.class);
if (bulletSpans.length > 0) {
BulletSpan startMarkSpan = bulletSpans[bulletSpans.length - 1];
int startIndex = output.getSpanStart(startMarkSpan);
output.removeSpan(startMarkSpan);
if (startIndex != outputLength) {
output.setSpan(
new LeadingMarginSpan.Standard(10),
startIndex,
outputLength,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
output.setSpan(
new BulletSpan(10),
startIndex,
outputLength,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if(text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
/**
* 样式情况判断
* @param editable editable
* @param start start
* @param end end
*/
public void applyStyle(Editable editable, int start, int end) {
//获取 从 start 到 end 位置上所有的指定 class 类型的 Span数组
E[] spans = editable.getSpans(start, end, clazzE);
E existingSpan = null;
if (spans.length > 0) {
existingSpan = spans[0];
}
if (existingSpan == null) {
//当前选中内部无此样式,开始设置span样式
checkAndMergeSpan(editable, start, end, clazzE);
} else {
//获取 一个 span 的起始位置
int existingSpanStart = editable.getSpanStart(existingSpan);
//获取一个span 的结束位置
int existingSpanEnd = editable.getSpanEnd(existingSpan);
if (existingSpanStart <= start && existingSpanEnd >= end) {
//在一个 完整的 span 中
//当我们选中的区域在一段连续的 Bold 样式里面的时候,再次选择Bold将会取消样式
//删除 样式
removeStyle(editable, start, end, clazzE, true);
} else {
//当前选中区域存在了某某样式,需要合并样式
checkAndMergeSpan(editable, start, end, clazzE);
}
}
}
/**
* Remove the TextChangedListeners
*/
protected void removeListeners() {
Editable text = getText();
if (text != null) {
TokenSpanWatcher[] spanWatchers = text.getSpans(0, text.length(), TokenSpanWatcher.class);
for (TokenSpanWatcher watcher : spanWatchers) {
text.removeSpan(watcher);
}
removeTextChangedListener(textWatcher);
}
}
private static Object getLast(Editable text, Class kind) {
Object[] spans = text.getSpans(0, text.length(), kind);
if (spans.length == 0) {
return null;
} else {
for (int i = spans.length; i > 0; i--) {
if (text.getSpanFlags(spans[i - 1]) == Spannable.SPAN_MARK_MARK) {
return spans[i - 1];
}
}
return null;
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length; i > 0; i--) {
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
return objs[i - 1];
}
}
return null;
}
}
private void checkForDeletions(Editable editable) {
Integer[] selected = getSelected();
boolean hasDeletions = false;
UserSpan[] spans = editable.getSpans(0, editable.length(), UserSpan.class);
for (Integer u : selected) {
boolean founded = false;
for (UserSpan span : spans) {
if (span.getUser().getId() == u) {
if (editable.getSpanStart(span) == editable.getSpanEnd(span)) {
break;
} else {
founded = true;
break;
}
}
}
if (!founded) {
hasDeletions = true;
unselect(u);
}
}
if (hasDeletions) {
getActivity().invalidateOptionsMenu();
getAdapter().notifyDataSetChanged();
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for(int i = objs.length;i>0;i--) {
if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
return objs[i-1];
}
}
return null;
}
}
/**
* Replaces any {@link com.linkedin.android.spyglass.ui.MentionsEditText.PlaceholderSpan} within the given text with
* the {@link MentionSpan} it contains.
*
* Note: These PlaceholderSpans are added in {@link #replaceMentionSpansWithPlaceholdersAsNecessary(CharSequence)}
*
* @param text the final version of the text after it was changed
*/
private void replacePlaceholdersWithCorrespondingMentionSpans(@NonNull Editable text) {
PlaceholderSpan[] tempSpans = text.getSpans(0, text.length(), PlaceholderSpan.class);
for (PlaceholderSpan span : tempSpans) {
int spanStart = text.getSpanStart(span);
String mentionDisplayString = span.holder.getDisplayString();
int end = Math.min(spanStart + mentionDisplayString.length(), text.length());
text.replace(spanStart, end, mentionDisplayString);
text.setSpan(span.holder, spanStart, spanStart + mentionDisplayString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
text.removeSpan(span);
}
}
/**
* Fetch the matching opening Annotation object and verify that it's the one added by us.
*
* @param output Spannable string we're working with.
* @return Starting Annotation object.
*/
private Object getOpeningAnnotation(Editable output) {
Object[] objs = output.getSpans(0, output.length(), Annotation.class);
for (int i = objs.length - 1; i >= 0; i--) {
Annotation span = (Annotation) objs[i];
if (output.getSpanFlags(objs[i]) == Spanned.SPAN_MARK_MARK
&& span.getKey().equals(IGNORED_ANNOTATION_KEY)
&& span.getValue().equals(IGNORED_ANNOTATION_VALUE)) {
return objs[i];
}
}
return null;
}
public void updateFormattingAtCursor() {
if (mEditText == null)
return;
Editable text = mEditText.getText();
int start = mEditText.getSelectionStart();
int end = mEditText.getSelectionEnd();
Object[] spans = text.getSpans(start, end, Object.class);
mBoldButton.setSelected(false);
mItalicButton.setSelected(false);
mUnderlineButton.setSelected(false);
int fgColor = -1;
int bgColor = -1;
for (Object span : spans) {
if (!SpannableStringHelper.checkSpanInclude(text, span, start, end) ||
(text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0)
continue;
if (span instanceof StyleSpan) {
int style = ((StyleSpan) span).getStyle();
if (style == Typeface.BOLD)
mBoldButton.setSelected(true);
else if (style == Typeface.ITALIC)
mItalicButton.setSelected(true);
} else if (span instanceof UnderlineSpan) {
mUnderlineButton.setSelected(true);
} else if (span instanceof ForegroundColorSpan) {
fgColor = ((ForegroundColorSpan) span).getForegroundColor();
} else if (span instanceof BackgroundColorSpan) {
bgColor = ((BackgroundColorSpan) span).getBackgroundColor();
}
}
ImageViewCompat.setImageTintList(mTextColorValue, fgColor != -1
? ColorStateList.valueOf(fgColor) : mTextColorValueDefault);
ImageViewCompat.setImageTintList(mFillColorValue, bgColor != -1
? ColorStateList.valueOf(bgColor) : mFillColorValueDefault);
}