下面列出了android.text.Editable#getSpanEnd ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// count > 0 means something will be deleted
if (count > 0 && getText() != null) {
Editable text = getText();
int end = start + count;
TokenImageSpan[] spans = text.getSpans(start, end, TokenImageSpan.class);
//NOTE: I'm not completely sure this won't cause problems if we get stuck in a text changed loop
//but it appears to work fine. Spans will stop getting removed if this breaks.
ArrayList<TokenImageSpan> spansToRemove = new ArrayList<>();
for (TokenImageSpan token : spans) {
if (text.getSpanStart(token) < end && start < text.getSpanEnd(token)) {
spansToRemove.add(token);
}
}
this.spansToRemove = spansToRemove;
}
}
/**
* Remove a span from the current EditText and fire the appropriate callback
*
* @param text Editable to remove the span from
* @param span TokenImageSpan to be removed
*/
private void removeSpan(Editable text, TokenImageSpan span) {
//We usually add whitespace after a token, so let's try to remove it as well if it's present
int end = text.getSpanEnd(span);
if (end < text.length() && text.charAt(end) == ' ') {
end += 1;
}
internalEditInProgress = true;
text.delete(text.getSpanStart(span), end);
internalEditInProgress = false;
if (allowCollapse && !isFocused()) {
updateCountSpan();
}
}
@Override
public void afterTextChanged(Editable s) {
final TagSpan span = willDelSpan;
log("TagSpanTextWatcher#willRemove#span:" + (span == null ? "null" : span.toString()));
if (span != null && span.willRemove) {
int start = s.getSpanStart(span);
int end = s.getSpanEnd(span);
// Remove the span
s.removeSpan(span);
// Remove the remaining emoticon text.
if (start != end) {
s.delete(start, end);
}
}
}
/**
* Temporarily remove MentionSpans that may interfere with composing text. Note that software keyboards are allowed
* to place arbitrary spans over the text. This was resulting in several bugs in edge cases while handling the
* MentionSpans while composing text (with different issues for different keyboards). The easiest solution for this
* is to remove any MentionSpans that could cause issues while the user is changing text.
*
* Note: The MentionSpans are added again in {@link #replacePlaceholdersWithCorrespondingMentionSpans(Editable)}
*
* @param text the current text before it changes
*/
private void replaceMentionSpansWithPlaceholdersAsNecessary(@NonNull CharSequence text) {
int index = getSelectionStart();
int wordStart = findStartOfWord(text, index);
Editable editable = getText();
MentionSpan[] mentionSpansInCurrentWord = editable.getSpans(wordStart, index, MentionSpan.class);
for (MentionSpan span : mentionSpansInCurrentWord) {
if (span.getDisplayMode() != Mentionable.MentionDisplayMode.NONE) {
int spanStart = editable.getSpanStart(span);
int spanEnd = editable.getSpanEnd(span);
editable.setSpan(new PlaceholderSpan(span, spanStart, spanEnd),
spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
editable.removeSpan(span);
}
}
}
@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);
}
@SuppressWarnings("WeakerAccess")
public void onClick() {
Editable text = getText();
if (text == null) return;
switch (tokenClickStyle) {
case Select:
case SelectDeselect:
if (!view.isSelected()) {
clearSelections();
view.setSelected(true);
break;
}
if (tokenClickStyle == TokenClickStyle.SelectDeselect || !isTokenRemovable(token)) {
view.setSelected(false);
invalidate();
break;
}
//If the view is already selected, we want to delete it
case Delete:
if (isTokenRemovable(token)) {
removeSpan(text, this);
}
break;
case None:
default:
if (getSelectionStart() != text.getSpanEnd(this)) {
//Make sure the selection is not in the middle of the span
setSelection(text.getSpanEnd(this));
}
}
}
public static void fix(final Editable editable) {
for (final URLSpan urlspan : editable.getSpans(0, editable.length() - 1, URLSpan.class)) {
final int start = editable.getSpanStart(urlspan);
final int end = editable.getSpanEnd(urlspan);
editable.removeSpan(urlspan);
editable.setSpan(new FixedURLSpan(urlspan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void replaceSpan(RecipientSpan span, Recipient newRecipient){
Editable text = getText();
int start = text.getSpanStart(span);
int end = text.getSpanEnd(span);
String replace = newRecipient.number;
text.replace(start, end - 2, newRecipient.number, 0, replace.length());
span.setRecipient(newRecipient);
text.setSpan(span, start, start + replace.length() + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
public void onClick() {
Editable text = getText();
if (text == null) return;
switch (tokenClickStyle) {
case Select:
case SelectDeselect:
if (!view.isSelected()) {
clearSelections();
view.setSelected(true);
break;
}
if (tokenClickStyle == TokenClickStyle.SelectDeselect) {
view.setSelected(false);
invalidate();
break;
}
//If the view is already selected, we want to delete it
case Delete:
removeSpan(this);
break;
case None:
default:
if (getSelectionStart() != text.getSpanEnd(this) + 1) {
//Make sure the selection is not in the middle of the span
setSelection(text.getSpanEnd(this) + 1);
}
}
}
/**
* 处理行内样式各个按钮的状态(点亮或置灰)
*
* @param type 样式类型
*/
private boolean handleInlineStyleButtonStatus(@RichTypeEnum String type) {
Editable editable = mRichEditText.getEditableText();
int cursorPos = mRichEditText.getSelectionEnd();
IInlineSpan[] inlineSpans = (IInlineSpan[]) editable.getSpans(cursorPos, cursorPos, getSpanClassFromType(type));
if (inlineSpans.length <= 0) {
return false;
}
boolean isLight = false; //是否点亮
for (IInlineSpan span : inlineSpans) {
int spanStart = editable.getSpanStart(span);
int spanEnd = editable.getSpanEnd(span);
int spanFlag = editable.getSpanFlags(span);
if (spanStart < cursorPos && spanEnd > cursorPos) {
isLight = true;
} else if (spanStart == cursorPos
&& (spanFlag == Spanned.SPAN_INCLUSIVE_INCLUSIVE || spanFlag == Spanned.SPAN_INCLUSIVE_EXCLUSIVE)) {
isLight = true;
} else if (spanEnd == cursorPos
&& (spanFlag == Spanned.SPAN_INCLUSIVE_INCLUSIVE || spanFlag == Spanned.SPAN_EXCLUSIVE_INCLUSIVE)) {
isLight = true;
}
}
return isLight;
}
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);
}
public static void fix(final Editable editable) {
for (final URLSpan urlspan : editable.getSpans(0, editable.length() - 1, URLSpan.class)) {
final int start = editable.getSpanStart(urlspan);
final int end = editable.getSpanEnd(urlspan);
editable.removeSpan(urlspan);
editable.setSpan(new FixedURLSpan(urlspan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void applyTextStyleToSelection(TypefaceSpan span) {
int start;
int end;
if (selectionStart >= 0 && selectionEnd >= 0) {
start = selectionStart;
end = selectionEnd;
selectionStart = selectionEnd = -1;
} else {
start = getSelectionStart();
end = getSelectionEnd();
}
Editable editable = getText();
CharacterStyle spans[] = editable.getSpans(start, end, CharacterStyle.class);
if (spans != null && spans.length > 0) {
for (int a = 0; a < spans.length; a++) {
CharacterStyle oldSpan = spans[a];
int spanStart = editable.getSpanStart(oldSpan);
int spanEnd = editable.getSpanEnd(oldSpan);
editable.removeSpan(oldSpan);
if (spanStart < start) {
editable.setSpan(oldSpan, spanStart, start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (spanEnd > end) {
editable.setSpan(oldSpan, end, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
if (span != null) {
editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (delegate != null) {
delegate.onSpansChanged();
}
}
/**
* Resets the given {@link MentionSpan} in the editor, forcing it to redraw with its latest drawable state.
*
* @param span the {@link MentionSpan} to update
*/
public void updateSpan(@NonNull MentionSpan span) {
mBlockCompletion = true;
Editable text = getText();
int start = text.getSpanStart(span);
int end = text.getSpanEnd(span);
if (start >= 0 && end > start && end <= text.length()) {
text.removeSpan(span);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mBlockCompletion = false;
}
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();
}
}
/**
* 样式情况判断
* @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);
}
}
}
/**
* Checks if selection can be deleted. This method is called from TokenInputConnection .
* @param beforeLength the number of characters before the current selection end to check
* @return true if there are no non-deletable pieces of the section
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean canDeleteSelection(int beforeLength) {
if (getObjects().size() < 1) return true;
// if beforeLength is 1, we either have no selection or the call is coming from OnKey Event.
// In these scenarios, getSelectionStart() will return the correct value.
int endSelection = getSelectionEnd();
int startSelection = beforeLength == 1 ? getSelectionStart() : endSelection - beforeLength;
Editable text = getText();
TokenImageSpan[] spans = text.getSpans(0, text.length(), TokenImageSpan.class);
// Iterate over all tokens and allow the deletion
// if there are no tokens not removable in the selection
for (TokenImageSpan span : spans) {
int startTokenSelection = text.getSpanStart(span);
int endTokenSelection = text.getSpanEnd(span);
// moving on, no need to check this token
if (isTokenRemovable(span.token)) continue;
if (startSelection == endSelection) {
// Delete single
if (endTokenSelection + 1 == endSelection) {
return false;
}
} else {
// Delete range
// Don't delete if a non removable token is in range
if (startSelection <= startTokenSelection
&& endTokenSelection + 1 <= endSelection) {
return false;
}
}
}
return true;
}
private void spellCheck() {
if (mSpellCheckerSession == null) return;
Editable editable = (Editable) mTextView.getText();
final int selectionStart = Selection.getSelectionStart(editable);
final int selectionEnd = Selection.getSelectionEnd(editable);
TextInfo[] textInfos = new TextInfo[mLength];
int textInfosCount = 0;
for (int i = 0; i < mLength; i++) {
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
// Do not check this word if the user is currently editing it
final boolean isEditing;
// Defer spell check when typing a word ending with a punctuation like an apostrophe
// which could end up being a mid-word punctuation.
if (selectionStart == end + 1
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
isEditing = false;
} else if (mIsSentenceSpellCheckSupported) {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
isEditing = selectionEnd <= start || selectionStart > end;
} else {
isEditing = selectionEnd < start || selectionStart > end;
}
if (start >= 0 && end > start && isEditing) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
if (DBG) {
Log.d(TAG, "create TextInfo: (" + i + "/" + mLength + ") text = "
+ textInfo.getSequence() + ", cookie = " + mCookie + ", seq = "
+ mIds[i] + ", sel start = " + selectionStart + ", sel end = "
+ selectionEnd + ", start = " + start + ", end = " + end);
}
}
}
if (textInfosCount > 0) {
if (textInfosCount < textInfos.length) {
TextInfo[] textInfosCopy = new TextInfo[textInfosCount];
System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount);
textInfos = textInfosCopy;
}
if (mIsSentenceSpellCheckSupported) {
mSpellCheckerSession.getSentenceSuggestions(
textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
} else {
mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
false /* TODO Set sequentialWords to true for initial spell check */);
}
}
}
private void updateHint() {
Editable text = getText();
CharSequence hintText = getHint();
if (text == null || hintText == null) {
return;
}
//Show hint if we need to
if (prefix.length() > 0) {
HintSpan[] hints = text.getSpans(0, text.length(), HintSpan.class);
HintSpan hint = null;
int testLength = prefix.length();
if (hints.length > 0) {
hint = hints[0];
testLength += text.getSpanEnd(hint) - text.getSpanStart(hint);
}
if (text.length() == testLength) {
hintVisible = true;
if (hint != null) {
return;//hint already visible
}
//We need to display the hint manually
Typeface tf = getTypeface();
int style = Typeface.NORMAL;
if (tf != null) {
style = tf.getStyle();
}
ColorStateList colors = getHintTextColors();
HintSpan hintSpan = new HintSpan(null, style, (int)getTextSize(), colors, colors);
text.insert(prefix.length(), hintText);
text.setSpan(hintSpan, prefix.length(), prefix.length() + getHint().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setSelection(prefix.length());
} else {
if (hint == null) {
return; //hint already removed
}
//Remove the hint. There should only ever be one
int sStart = text.getSpanStart(hint);
int sEnd = text.getSpanEnd(hint);
text.removeSpan(hint);
text.replace(sStart, sEnd, "");
hintVisible = false;
}
}
}
/**
* Ensures that the text within each {@link MentionSpan} in the {@link Editable} correctly
* matches what it should be outputting. If not, replace it with the correct value.
*
* @param text the {@link Editable} to examine
*/
private void ensureMentionSpanIntegrity(Editable text) {
if (text == null) {
return;
}
MentionSpan[] spans = text.getSpans(0, text.length(), MentionSpan.class);
boolean spanAltered = false;
for (MentionSpan span : spans) {
int start = text.getSpanStart(span);
int end = text.getSpanEnd(span);
CharSequence spanText = text.subSequence(start, end).toString();
Mentionable.MentionDisplayMode displayMode = span.getDisplayMode();
switch (displayMode) {
case PARTIAL:
case FULL:
String name = span.getDisplayString();
if (!name.contentEquals(spanText) && start >= 0 && start < end && end <= text.length()) {
// Mention display name does not match what is being shown,
// replace text in span with proper display name
int cursor = getSelectionStart();
int diff = cursor - end;
text.removeSpan(span);
text.replace(start, end, name);
if (diff > 0 && start + end + diff < text.length()) {
text.replace(start + end, start + end + diff, "");
}
if (name.length() > 0) {
text.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// Notify for partially deleted mentions.
if (mMentionWatchers.size() > 0 && displayMode == Mentionable.MentionDisplayMode.PARTIAL) {
notifyMentionPartiallyDeletedWatchers(span.getMention(), name, start, end);
}
spanAltered = true;
}
break;
case NONE:
default:
// Mention with DisplayMode == NONE should be deleted from the text
boolean hasListeners = mMentionWatchers.size() > 0;
final String textBeforeDelete = hasListeners ? text.toString() : null;
text.delete(start, end);
setSelection(start);
if (hasListeners) {
notifyMentionDeletedWatchers(span.getMention(), textBeforeDelete, start, end);
}
spanAltered = true;
break;
}
}
// Reset input method if spans have been changed (updates suggestions)
if (spanAltered) {
restartInput();
}
}