SpannableStringBuilderpublic class SpannableStringBuilder extends Object implements GraphicsOperations, Appendable, CharSequence, Editable, GetChars, SpannableThis is the class for text whose content and markup can both be changed. |
Fields Summary |
---|
private static final String | TAG | private static final InputFilter[] | NO_FILTERS | private InputFilter[] | mFilters | private char[] | mText | private int | mGapStart | private int | mGapLength | private Object[] | mSpans | private int[] | mSpanStarts | private int[] | mSpanEnds | private int[] | mSpanFlags | private int | mSpanCount | private int | mSpanCountBeforeAdd | private static final int | MARK | private static final int | POINT | private static final int | PARAGRAPH | private static final int | START_MASK | private static final int | END_MASK | private static final int | START_SHIFT | private static final int | SPAN_START_AT_START | private static final int | SPAN_START_AT_END | private static final int | SPAN_END_AT_START | private static final int | SPAN_END_AT_END | private static final int | SPAN_START_END_MASK |
Constructors Summary |
---|
public SpannableStringBuilder()Create a new SpannableStringBuilder with empty contents
this("");
| public SpannableStringBuilder(CharSequence text)Create a new SpannableStringBuilder containing a copy of the
specified text, including its spans if any.
this(text, 0, text.length());
| public SpannableStringBuilder(CharSequence text, int start, int end)Create a new SpannableStringBuilder containing a copy of the
specified slice of the specified text, including its spans if any.
int srclen = end - start;
if (srclen < 0) throw new StringIndexOutOfBoundsException();
mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
mGapStart = srclen;
mGapLength = mText.length - srclen;
TextUtils.getChars(text, start, end, mText, 0);
mSpanCount = 0;
mSpans = EmptyArray.OBJECT;
mSpanStarts = EmptyArray.INT;
mSpanEnds = EmptyArray.INT;
mSpanFlags = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
Object[] spans = sp.getSpans(start, end, Object.class);
for (int i = 0; i < spans.length; i++) {
if (spans[i] instanceof NoCopySpan) {
continue;
}
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
if (st < 0)
st = 0;
if (st > end - start)
st = end - start;
if (en < 0)
en = 0;
if (en > end - start)
en = end - start;
setSpan(false, spans[i], st, en, fl);
}
}
|
Methods Summary |
---|
public android.text.SpannableStringBuilder | append(java.lang.CharSequence text)
int length = length();
return replace(length, length, text, 0, text.length());
| public android.text.SpannableStringBuilder | append(java.lang.CharSequence text, java.lang.Object what, int flags)Appends the character sequence {@code text} and spans {@code what} over the appended part.
See {@link Spanned} for an explanation of what the flags mean.
int start = length();
append(text);
setSpan(what, start, length(), flags);
return this;
| public android.text.SpannableStringBuilder | append(java.lang.CharSequence text, int start, int end)
int length = length();
return replace(length, length, text, start, end);
| public android.text.SpannableStringBuilder | append(char text)
return append(String.valueOf(text));
| private void | change(int start, int end, java.lang.CharSequence cs, int csStart, int csEnd)
// Can be negative
final int replacedLength = end - start;
final int replacementLength = csEnd - csStart;
final int nbNewChars = replacementLength - replacedLength;
for (int i = mSpanCount - 1; i >= 0; i--) {
int spanStart = mSpanStarts[i];
if (spanStart > mGapStart)
spanStart -= mGapLength;
int spanEnd = mSpanEnds[i];
if (spanEnd > mGapStart)
spanEnd -= mGapLength;
if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
int ost = spanStart;
int oen = spanEnd;
int clen = length();
if (spanStart > start && spanStart <= end) {
for (spanStart = end; spanStart < clen; spanStart++)
if (spanStart > end && charAt(spanStart - 1) == '\n")
break;
}
if (spanEnd > start && spanEnd <= end) {
for (spanEnd = end; spanEnd < clen; spanEnd++)
if (spanEnd > end && charAt(spanEnd - 1) == '\n")
break;
}
if (spanStart != ost || spanEnd != oen)
setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
}
int flags = 0;
if (spanStart == start) flags |= SPAN_START_AT_START;
else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
if (spanEnd == start) flags |= SPAN_END_AT_START;
else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
mSpanFlags[i] |= flags;
}
moveGapTo(end);
if (nbNewChars >= mGapLength) {
resizeFor(mText.length + nbNewChars - mGapLength);
}
final boolean textIsRemoved = replacementLength == 0;
// The removal pass needs to be done before the gap is updated in order to broadcast the
// correct previous positions to the correct intersecting SpanWatchers
if (replacedLength > 0) { // no need for span fixup on pure insertion
// A for loop will not work because the array is being modified
// Do not iterate in reverse to keep the SpanWatchers notified in ordering
// Also, a removed SpanWatcher should not get notified of removed spans located
// further in the span array.
int i = 0;
while (i < mSpanCount) {
if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
// This condition indicates that the span would become empty
(textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
removeSpan(i);
continue; // do not increment i, spans will be shifted left in the array
}
i++;
}
}
mGapStart += nbNewChars;
mGapLength -= nbNewChars;
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
TextUtils.getChars(cs, csStart, csEnd, mText, start);
if (replacedLength > 0) { // no need for span fixup on pure insertion
final boolean atEnd = (mGapStart + mGapLength == mText.length);
for (int i = 0; i < mSpanCount; i++) {
final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
atEnd, textIsRemoved);
final int endFlag = (mSpanFlags[i] & END_MASK);
mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
atEnd, textIsRemoved);
}
}
mSpanCountBeforeAdd = mSpanCount;
if (cs instanceof Spanned) {
Spanned sp = (Spanned) cs;
Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
for (int i = 0; i < spans.length; i++) {
int st = sp.getSpanStart(spans[i]);
int en = sp.getSpanEnd(spans[i]);
if (st < csStart) st = csStart;
if (en > csEnd) en = csEnd;
// Add span only if this object is not yet used as a span in this string
if (getSpanStart(spans[i]) < 0) {
setSpan(false, spans[i], st - csStart + start, en - csStart + start,
sp.getSpanFlags(spans[i]));
}
}
}
| public char | charAt(int where)Return the char at the specified offset within the buffer.
int len = length();
if (where < 0) {
throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
} else if (where >= len) {
throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
}
if (where >= mGapStart)
return mText[where + mGapLength];
else
return mText[where];
| private void | checkRange(java.lang.String operation, int start, int end)
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " starts before 0");
}
| public void | clear()
replace(0, length(), "", 0, 0);
| public void | clearSpans()
for (int i = mSpanCount - 1; i >= 0; i--) {
Object what = mSpans[i];
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
mSpanCount = i;
mSpans[i] = null;
sendSpanRemoved(what, ostart, oend);
}
| public android.text.SpannableStringBuilder | delete(int start, int end)
SpannableStringBuilder ret = replace(start, end, "", 0, 0);
if (mGapLength > 2 * length())
resizeFor(length());
return ret; // == this
| public void | drawText(android.graphics.Canvas c, int start, int end, float x, float y, android.graphics.Paint p)Don't call this yourself -- exists for Canvas to use internally.
{@hide}
checkRange("drawText", start, end);
if (end <= mGapStart) {
c.drawText(mText, start, end - start, x, y, p);
} else if (start >= mGapStart) {
c.drawText(mText, start + mGapLength, end - start, x, y, p);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
c.drawText(buf, 0, end - start, x, y, p);
TextUtils.recycle(buf);
}
| public void | drawTextRun(android.graphics.Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, android.graphics.Paint p)Don't call this yourself -- exists for Canvas to use internally.
{@hide}
checkRange("drawTextRun", start, end);
int contextLen = contextEnd - contextStart;
int len = end - start;
if (contextEnd <= mGapStart) {
c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
} else if (contextStart >= mGapStart) {
c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
contextLen, x, y, isRtl, p);
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
TextUtils.recycle(buf);
}
| public boolean | equals(java.lang.Object o)
if (o instanceof Spanned &&
toString().equals(o.toString())) {
Spanned other = (Spanned) o;
// Check span data
Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
if (mSpanCount == otherSpans.length) {
for (int i = 0; i < mSpanCount; ++i) {
Object thisSpan = mSpans[i];
Object otherSpan = otherSpans[i];
if (thisSpan == this) {
if (other != otherSpan ||
getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
return false;
}
} else if (!thisSpan.equals(otherSpan) ||
getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
return false;
}
}
return true;
}
}
return false;
| public void | getChars(int start, int end, char[] dest, int destoff)Copy the specified range of chars from this buffer into the
specified array, beginning at the specified offset.
checkRange("getChars", start, end);
if (end <= mGapStart) {
System.arraycopy(mText, start, dest, destoff, end - start);
} else if (start >= mGapStart) {
System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
} else {
System.arraycopy(mText, start, dest, destoff, mGapStart - start);
System.arraycopy(mText, mGapStart + mGapLength,
dest, destoff + (mGapStart - start),
end - mGapStart);
}
| public InputFilter[] | getFilters()
return mFilters;
| public int | getSpanEnd(java.lang.Object what)Return the buffer offset of the end of the specified
markup object, or -1 if it is not attached to this buffer.
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
int where = mSpanEnds[i];
if (where > mGapStart)
where -= mGapLength;
return where;
}
}
return -1;
| public int | getSpanFlags(java.lang.Object what)Return the flags of the end of the specified
markup object, or 0 if it is not attached to this buffer.
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
return mSpanFlags[i];
}
}
return 0;
| public int | getSpanStart(java.lang.Object what)Return the buffer offset of the beginning of the specified
markup object, or -1 if it is not attached to this buffer.
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
int where = mSpanStarts[i];
if (where > mGapStart)
where -= mGapLength;
return where;
}
}
return -1;
| public T[] | getSpans(int queryStart, int queryEnd, java.lang.Class kind)Return an array of the spans of the specified type that overlap
the specified range of the buffer. The kind may be Object.class to get
a list of all the spans regardless of type.
if (kind == null) return ArrayUtils.emptyArray(kind);
int spanCount = mSpanCount;
Object[] spans = mSpans;
int[] starts = mSpanStarts;
int[] ends = mSpanEnds;
int[] flags = mSpanFlags;
int gapstart = mGapStart;
int gaplen = mGapLength;
int count = 0;
T[] ret = null;
T ret1 = null;
for (int i = 0; i < spanCount; i++) {
int spanStart = starts[i];
if (spanStart > gapstart) {
spanStart -= gaplen;
}
if (spanStart > queryEnd) {
continue;
}
int spanEnd = ends[i];
if (spanEnd > gapstart) {
spanEnd -= gaplen;
}
if (spanEnd < queryStart) {
continue;
}
if (spanStart != spanEnd && queryStart != queryEnd) {
if (spanStart == queryEnd)
continue;
if (spanEnd == queryStart)
continue;
}
// Expensive test, should be performed after the previous tests
if (!kind.isInstance(spans[i])) continue;
if (count == 0) {
// Safe conversion thanks to the isInstance test above
ret1 = (T) spans[i];
count++;
} else {
if (count == 1) {
// Safe conversion, but requires a suppressWarning
ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
ret[0] = ret1;
}
int prio = flags[i] & SPAN_PRIORITY;
if (prio != 0) {
int j;
for (j = 0; j < count; j++) {
int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
if (prio > p) {
break;
}
}
System.arraycopy(ret, j, ret, j + 1, count - j);
// Safe conversion thanks to the isInstance test above
ret[j] = (T) spans[i];
count++;
} else {
// Safe conversion thanks to the isInstance test above
ret[count++] = (T) spans[i];
}
}
}
if (count == 0) {
return ArrayUtils.emptyArray(kind);
}
if (count == 1) {
// Safe conversion, but requires a suppressWarning
ret = (T[]) Array.newInstance(kind, 1);
ret[0] = ret1;
return ret;
}
if (count == ret.length) {
return ret;
}
// Safe conversion, but requires a suppressWarning
T[] nret = (T[]) Array.newInstance(kind, count);
System.arraycopy(ret, 0, nret, 0, count);
return nret;
| public float | getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesPos, android.graphics.Paint p)Don't call this yourself -- exists for Paint to use internally.
{@hide}
float ret;
int contextLen = contextEnd - contextStart;
int len = end - start;
if (end <= mGapStart) {
ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
isRtl, advances, advancesPos);
} else if (start >= mGapStart) {
ret = p.getTextRunAdvances(mText, start + mGapLength, len,
contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
ret = p.getTextRunAdvances(buf, start - contextStart, len,
0, contextLen, isRtl, advances, advancesPos);
TextUtils.recycle(buf);
}
return ret;
| public int | getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, android.graphics.Paint p)Returns the next cursor position in the run. This avoids placing the cursor between
surrogates, between characters that form conjuncts, between base characters and combining
marks, or within a reordering cluster.
The context is the shaping context for cursor movement, generally the bounds of the metric
span enclosing the cursor in the direction of movement.
contextStart , contextEnd and offset are relative to
the start of the string.
If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
this returns -1. Otherwise this will never return a value before contextStart or after
contextEnd.
int ret;
int contextLen = contextEnd - contextStart;
if (contextEnd <= mGapStart) {
ret = p.getTextRunCursor(mText, contextStart, contextLen,
dir, offset, cursorOpt);
} else if (contextStart >= mGapStart) {
ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
dir, offset + mGapLength, cursorOpt) - mGapLength;
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
ret = p.getTextRunCursor(buf, 0, contextLen,
dir, offset - contextStart, cursorOpt) + contextStart;
TextUtils.recycle(buf);
}
return ret;
| public int | getTextWidths(int start, int end, float[] widths, android.graphics.Paint p)Don't call this yourself -- exists for Paint to use internally.
{@hide}
checkRange("getTextWidths", start, end);
int ret;
if (end <= mGapStart) {
ret = p.getTextWidths(mText, start, end - start, widths);
} else if (start >= mGapStart) {
ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
ret = p.getTextWidths(buf, 0, end - start, widths);
TextUtils.recycle(buf);
}
return ret;
| private static boolean | hasNonExclusiveExclusiveSpanAt(java.lang.CharSequence text, int offset)
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
Object[] spans = spanned.getSpans(offset, offset, Object.class);
final int length = spans.length;
for (int i = 0; i < length; i++) {
Object span = spans[i];
int flags = spanned.getSpanFlags(span);
if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
}
}
return false;
| public int | hashCode()
int hash = toString().hashCode();
hash = hash * 31 + mSpanCount;
for (int i = 0; i < mSpanCount; ++i) {
Object span = mSpans[i];
if (span != this) {
hash = hash * 31 + span.hashCode();
}
hash = hash * 31 + getSpanStart(span);
hash = hash * 31 + getSpanEnd(span);
hash = hash * 31 + getSpanFlags(span);
}
return hash;
| public android.text.SpannableStringBuilder | insert(int where, java.lang.CharSequence tb)
return replace(where, where, tb, 0, tb.length());
| public android.text.SpannableStringBuilder | insert(int where, java.lang.CharSequence tb, int start, int end)
return replace(where, where, tb, start, end);
| public int | length()Return the number of chars in the buffer.
return mText.length - mGapLength;
| public float | measureText(int start, int end, android.graphics.Paint p)Don't call this yourself -- exists for Paint to use internally.
{@hide}
checkRange("measureText", start, end);
float ret;
if (end <= mGapStart) {
ret = p.measureText(mText, start, end - start);
} else if (start >= mGapStart) {
ret = p.measureText(mText, start + mGapLength, end - start);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
ret = p.measureText(buf, 0, end - start);
TextUtils.recycle(buf);
}
return ret;
| private void | moveGapTo(int where)
if (where == mGapStart)
return;
boolean atEnd = (where == length());
if (where < mGapStart) {
int overlap = mGapStart - where;
System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
} else /* where > mGapStart */ {
int overlap = where - mGapStart;
System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
}
// XXX be more clever
for (int i = 0; i < mSpanCount; i++) {
int start = mSpanStarts[i];
int end = mSpanEnds[i];
if (start > mGapStart)
start -= mGapLength;
if (start > where)
start += mGapLength;
else if (start == where) {
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
if (flag == POINT || (atEnd && flag == PARAGRAPH))
start += mGapLength;
}
if (end > mGapStart)
end -= mGapLength;
if (end > where)
end += mGapLength;
else if (end == where) {
int flag = (mSpanFlags[i] & END_MASK);
if (flag == POINT || (atEnd && flag == PARAGRAPH))
end += mGapLength;
}
mSpanStarts[i] = start;
mSpanEnds[i] = end;
}
mGapStart = where;
| public int | nextSpanTransition(int start, int limit, java.lang.Class kind)Return the next offset after start but less than or
equal to limit where a span of the specified type
begins or ends.
int count = mSpanCount;
Object[] spans = mSpans;
int[] starts = mSpanStarts;
int[] ends = mSpanEnds;
int gapstart = mGapStart;
int gaplen = mGapLength;
if (kind == null) {
kind = Object.class;
}
for (int i = 0; i < count; i++) {
int st = starts[i];
int en = ends[i];
if (st > gapstart)
st -= gaplen;
if (en > gapstart)
en -= gaplen;
if (st > start && st < limit && kind.isInstance(spans[i]))
limit = st;
if (en > start && en < limit && kind.isInstance(spans[i]))
limit = en;
}
return limit;
| private static java.lang.String | region(int start, int end)
return "(" + start + " ... " + end + ")";
| private void | removeSpan(int i)
Object object = mSpans[i];
int start = mSpanStarts[i];
int end = mSpanEnds[i];
if (start > mGapStart) start -= mGapLength;
if (end > mGapStart) end -= mGapLength;
int count = mSpanCount - (i + 1);
System.arraycopy(mSpans, i + 1, mSpans, i, count);
System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
mSpanCount--;
mSpans[mSpanCount] = null;
sendSpanRemoved(object, start, end);
| public void | removeSpan(java.lang.Object what)Remove the specified markup object from the buffer.
for (int i = mSpanCount - 1; i >= 0; i--) {
if (mSpans[i] == what) {
removeSpan(i);
return;
}
}
| public android.text.SpannableStringBuilder | replace(int start, int end, java.lang.CharSequence tb)
return replace(start, end, tb, 0, tb.length());
| public android.text.SpannableStringBuilder | replace(int start, int end, java.lang.CharSequence tb, int tbstart, int tbend)
checkRange("replace", start, end);
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
if (repl != null) {
tb = repl;
tbstart = 0;
tbend = repl.length();
}
}
final int origLen = end - start;
final int newLen = tbend - tbstart;
if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
// This is a no-op iif there are no spans in tb that would be added (with a 0-length)
// Early exit so that the text watchers do not get notified
return this;
}
TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
sendBeforeTextChanged(textWatchers, start, origLen, newLen);
// Try to keep the cursor / selection at the same relative position during
// a text replacement. If replaced or replacement text length is zero, this
// is already taken care of.
boolean adjustSelection = origLen != 0 && newLen != 0;
int selectionStart = 0;
int selectionEnd = 0;
if (adjustSelection) {
selectionStart = Selection.getSelectionStart(this);
selectionEnd = Selection.getSelectionEnd(this);
}
change(start, end, tb, tbstart, tbend);
if (adjustSelection) {
if (selectionStart > start && selectionStart < end) {
final int offset = (selectionStart - start) * newLen / origLen;
selectionStart = start + offset;
setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Spanned.SPAN_POINT_POINT);
}
if (selectionEnd > start && selectionEnd < end) {
final int offset = (selectionEnd - start) * newLen / origLen;
selectionEnd = start + offset;
setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
Spanned.SPAN_POINT_POINT);
}
}
sendTextChanged(textWatchers, start, origLen, newLen);
sendAfterTextChanged(textWatchers);
// Span watchers need to be called after text watchers, which may update the layout
sendToSpanWatchers(start, end, newLen - origLen);
return this;
| private void | resizeFor(int size)
final int oldLength = mText.length;
if (size + 1 <= oldLength) {
return;
}
char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
System.arraycopy(mText, 0, newText, 0, mGapStart);
final int newLength = newText.length;
final int delta = newLength - oldLength;
final int after = oldLength - (mGapStart + mGapLength);
System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
mText = newText;
mGapLength += delta;
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
for (int i = 0; i < mSpanCount; i++) {
if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
}
| private void | sendAfterTextChanged(TextWatcher[] watchers)
int n = watchers.length;
for (int i = 0; i < n; i++) {
watchers[i].afterTextChanged(this);
}
| private void | sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after)
int n = watchers.length;
for (int i = 0; i < n; i++) {
watchers[i].beforeTextChanged(this, start, before, after);
}
| private void | sendSpanAdded(java.lang.Object what, int start, int end)
SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanAdded(this, what, start, end);
}
| private void | sendSpanChanged(java.lang.Object what, int oldStart, int oldEnd, int start, int end)
// The bounds of a possible SpanWatcher are guaranteed to be set before this method is
// called, so that the order of the span does not affect this broadcast.
SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
int n = spanWatchers.length;
for (int i = 0; i < n; i++) {
spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
}
| private void | sendSpanRemoved(java.lang.Object what, int start, int end)
SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanRemoved(this, what, start, end);
}
| private void | sendTextChanged(TextWatcher[] watchers, int start, int before, int after)
int n = watchers.length;
for (int i = 0; i < n; i++) {
watchers[i].onTextChanged(this, start, before, after);
}
| private void | sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars)
for (int i = 0; i < mSpanCountBeforeAdd; i++) {
int spanStart = mSpanStarts[i];
int spanEnd = mSpanEnds[i];
if (spanStart > mGapStart) spanStart -= mGapLength;
if (spanEnd > mGapStart) spanEnd -= mGapLength;
int spanFlags = mSpanFlags[i];
int newReplaceEnd = replaceEnd + nbNewChars;
boolean spanChanged = false;
int previousSpanStart = spanStart;
if (spanStart > newReplaceEnd) {
if (nbNewChars != 0) {
previousSpanStart -= nbNewChars;
spanChanged = true;
}
} else if (spanStart >= replaceStart) {
// No change if span start was already at replace interval boundaries before replace
if ((spanStart != replaceStart ||
((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
(spanStart != newReplaceEnd ||
((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
// TODO A correct previousSpanStart cannot be computed at this point.
// It would require to save all the previous spans' positions before the replace
// Using an invalid -1 value to convey this would break the broacast range
spanChanged = true;
}
}
int previousSpanEnd = spanEnd;
if (spanEnd > newReplaceEnd) {
if (nbNewChars != 0) {
previousSpanEnd -= nbNewChars;
spanChanged = true;
}
} else if (spanEnd >= replaceStart) {
// No change if span start was already at replace interval boundaries before replace
if ((spanEnd != replaceStart ||
((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
(spanEnd != newReplaceEnd ||
((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
// TODO same as above for previousSpanEnd
spanChanged = true;
}
}
if (spanChanged) {
sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
}
mSpanFlags[i] &= ~SPAN_START_END_MASK;
}
// The spans starting at mIntermediateSpanCount were added from the replacement text
for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
int spanStart = mSpanStarts[i];
int spanEnd = mSpanEnds[i];
if (spanStart > mGapStart) spanStart -= mGapLength;
if (spanEnd > mGapStart) spanEnd -= mGapLength;
sendSpanAdded(mSpans[i], spanStart, spanEnd);
}
| public void | setFilters(InputFilter[] filters)
if (filters == null) {
throw new IllegalArgumentException();
}
mFilters = filters;
| public void | setSpan(java.lang.Object what, int start, int end, int flags)Mark the specified range of text with the specified object.
The flags determine how the span will behave when text is
inserted at the start or end of the span's range.
setSpan(true, what, start, end, flags);
| private void | setSpan(boolean send, java.lang.Object what, int start, int end, int flags)
checkRange("setSpan", start, end);
int flagsStart = (flags & START_MASK) >> START_SHIFT;
if (flagsStart == PARAGRAPH) {
if (start != 0 && start != length()) {
char c = charAt(start - 1);
if (c != '\n")
throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
}
}
int flagsEnd = flags & END_MASK;
if (flagsEnd == PARAGRAPH) {
if (end != 0 && end != length()) {
char c = charAt(end - 1);
if (c != '\n")
throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
}
}
// 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
if (flagsStart == POINT && flagsEnd == MARK && start == end) {
if (send) {
Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
}
// Silently ignore invalid spans when they are created from this class.
// This avoids the duplication of the above test code before all the
// calls to setSpan that are done in this class
return;
}
int nstart = start;
int nend = end;
if (start > mGapStart) {
start += mGapLength;
} else if (start == mGapStart) {
if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
start += mGapLength;
}
if (end > mGapStart) {
end += mGapLength;
} else if (end == mGapStart) {
if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
end += mGapLength;
}
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = 0; i < count; i++) {
if (spans[i] == what) {
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
mSpanStarts[i] = start;
mSpanEnds[i] = end;
mSpanFlags[i] = flags;
if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
return;
}
}
mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
mSpanCount++;
if (send) sendSpanAdded(what, nstart, nend);
| public java.lang.CharSequence | subSequence(int start, int end)Return a new CharSequence containing a copy of the specified
range of this buffer, including the overlapping spans.
return new SpannableStringBuilder(this, start, end);
| public java.lang.String | substring(int start, int end)Return a String containing a copy of the chars in this buffer, limited to the
[start, end[ range.
char[] buf = new char[end - start];
getChars(start, end, buf, 0);
return new String(buf);
| public java.lang.String | toString()Return a String containing a copy of the chars in this buffer.
int len = length();
char[] buf = new char[len];
getChars(0, len, buf, 0);
return new String(buf);
| private int | updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, boolean textIsRemoved)
if (offset >= start && offset < mGapStart + mGapLength) {
if (flag == POINT) {
// A POINT located inside the replaced range should be moved to the end of the
// replaced text.
// The exception is when the point is at the start of the range and we are doing a
// text replacement (as opposed to a deletion): the point stays there.
if (textIsRemoved || offset > start) {
return mGapStart + mGapLength;
}
} else {
if (flag == PARAGRAPH) {
if (atEnd) {
return mGapStart + mGapLength;
}
} else { // MARK
// MARKs should be moved to the start, with the exception of a mark located at
// the end of the range (which will be < mGapStart + mGapLength since mGapLength
// is > 0, which should stay 'unchanged' at the end of the replaced text.
if (textIsRemoved || offset < mGapStart - nbNewChars) {
return start;
} else {
// Move to the end of replaced text (needed if nbNewChars != 0)
return mGapStart;
}
}
}
}
return offset;
| public static android.text.SpannableStringBuilder | valueOf(java.lang.CharSequence source)
if (source instanceof SpannableStringBuilder) {
return (SpannableStringBuilder) source;
} else {
return new SpannableStringBuilder(source);
}
|
|