|
Android example source code file (SpannableStringBuilder.java)
The SpannableStringBuilder.java Android example source code/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.text; import com.android.internal.util.ArrayUtils; import android.graphics.Paint; import android.graphics.Canvas; import java.lang.reflect.Array; /** * This is the class for text whose content and markup can both be changed. */ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable, Appendable, GraphicsOperations { /** * Create a new SpannableStringBuilder with empty contents */ public SpannableStringBuilder() { this(""); } /** * Create a new SpannableStringBuilder containing a copy of the * specified text, including its spans if any. */ public SpannableStringBuilder(CharSequence text) { this(text, 0, text.length()); } /** * Create a new SpannableStringBuilder containing a copy of the * specified slice of the specified text, including its spans if any. */ public SpannableStringBuilder(CharSequence text, int start, int end) { int srclen = end - start; int len = ArrayUtils.idealCharArraySize(srclen + 1); mText = new char[len]; mGapStart = srclen; mGapLength = len - srclen; TextUtils.getChars(text, start, end, mText, 0); mSpanCount = 0; int alloc = ArrayUtils.idealIntArraySize(0); mSpans = new Object[alloc]; mSpanStarts = new int[alloc]; mSpanEnds = new int[alloc]; mSpanFlags = new int[alloc]; 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(spans[i], st, en, fl); } } } public static SpannableStringBuilder valueOf(CharSequence source) { if (source instanceof SpannableStringBuilder) { return (SpannableStringBuilder) source; } else { return new SpannableStringBuilder(source); } } /** * Return the char at the specified offset within the buffer. */ public char charAt(int where) { 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]; } /** * Return the number of chars in the buffer. */ public int length() { return mText.length - mGapLength; } private void resizeFor(int size) { int newlen = ArrayUtils.idealCharArraySize(size + 1); char[] newtext = new char[newlen]; int after = mText.length - (mGapStart + mGapLength); System.arraycopy(mText, 0, newtext, 0, mGapStart); System.arraycopy(mText, mText.length - after, newtext, newlen - after, after); for (int i = 0; i < mSpanCount; i++) { if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += newlen - mText.length; if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += newlen - mText.length; } int oldlen = mText.length; mText = newtext; mGapLength += mText.length - oldlen; if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); } 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; } // Documentation from interface public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) { return replace(where, where, tb, start, end); } // Documentation from interface public SpannableStringBuilder insert(int where, CharSequence tb) { return replace(where, where, tb, 0, tb.length()); } // Documentation from interface public SpannableStringBuilder delete(int start, int end) { SpannableStringBuilder ret = replace(start, end, "", 0, 0); if (mGapLength > 2 * length()) resizeFor(length()); return ret; // == this } // Documentation from interface public void clear() { replace(0, length(), "", 0, 0); } // Documentation from interface 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); } } // Documentation from interface public SpannableStringBuilder append(CharSequence text) { int length = length(); return replace(length, length, text, 0, text.length()); } // Documentation from interface public SpannableStringBuilder append(CharSequence text, int start, int end) { int length = length(); return replace(length, length, text, start, end); } // Documentation from interface public SpannableStringBuilder append(char text) { return append(String.valueOf(text)); } private int change(int start, int end, CharSequence tb, int tbstart, int tbend) { return change(true, start, end, tb, tbstart, tbend); } private int change(boolean notify, int start, int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); int ret = tbend - tbstart; TextWatcher[] recipients = null; if (notify) recipients = sendTextWillChange(start, end - start, tbend - tbstart); for (int i = mSpanCount - 1; i >= 0; i--) { if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { int st = mSpanStarts[i]; if (st > mGapStart) st -= mGapLength; int en = mSpanEnds[i]; if (en > mGapStart) en -= mGapLength; int ost = st; int oen = en; int clen = length(); if (st > start && st <= end) { for (st = end; st < clen; st++) if (st > end && charAt(st - 1) == '\n') break; } if (en > start && en <= end) { for (en = end; en < clen; en++) if (en > end && charAt(en - 1) == '\n') break; } if (st != ost || en != oen) setSpan(mSpans[i], st, en, mSpanFlags[i]); } } moveGapTo(end); if (tbend - tbstart >= mGapLength + (end - start)) resizeFor(mText.length - mGapLength + tbend - tbstart - (end - start)); mGapStart += tbend - tbstart - (end - start); mGapLength -= tbend - tbstart - (end - start); if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); TextUtils.getChars(tb, tbstart, tbend, mText, start); if (tb instanceof Spanned) { Spanned sp = (Spanned) tb; Object[] spans = sp.getSpans(tbstart, tbend, Object.class); for (int i = 0; i < spans.length; i++) { int st = sp.getSpanStart(spans[i]); int en = sp.getSpanEnd(spans[i]); if (st < tbstart) st = tbstart; if (en > tbend) en = tbend; if (getSpanStart(spans[i]) < 0) { setSpan(false, spans[i], st - tbstart + start, en - tbstart + start, sp.getSpanFlags(spans[i])); } } } // no need for span fixup on pure insertion if (tbend > tbstart && end - start == 0) { if (notify) { sendTextChange(recipients, start, end - start, tbend - tbstart); sendTextHasChanged(recipients); } return ret; } boolean atend = (mGapStart + mGapLength == mText.length); for (int i = mSpanCount - 1; i >= 0; i--) { if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; if (flag == POINT || (flag == PARAGRAPH && atend)) mSpanStarts[i] = mGapStart + mGapLength; else mSpanStarts[i] = start; } if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & END_MASK); if (flag == POINT || (flag == PARAGRAPH && atend)) mSpanEnds[i] = mGapStart + mGapLength; else mSpanEnds[i] = start; } // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE // XXX send notification on removal if (mSpanEnds[i] < mSpanStarts[i]) { System.arraycopy(mSpans, i + 1, mSpans, i, mSpanCount - (i + 1)); System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, mSpanCount - (i + 1)); System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, mSpanCount - (i + 1)); System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, mSpanCount - (i + 1)); mSpanCount--; } } if (notify) { sendTextChange(recipients, start, end - start, tbend - tbstart); sendTextHasChanged(recipients); } return ret; } // Documentation from interface public SpannableStringBuilder replace(int start, int end, CharSequence tb) { return replace(start, end, tb, 0, tb.length()); } // Documentation from interface public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) { 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(); } } if (end == start && tbstart == tbend) { return this; } if (end == start || tbstart == tbend) { change(start, end, tb, tbstart, tbend); } else { int selstart = Selection.getSelectionStart(this); int selend = Selection.getSelectionEnd(this); // XXX just make the span fixups in change() do the right thing // instead of this madness! checkRange("replace", start, end); moveGapTo(end); TextWatcher[] recipients; recipients = sendTextWillChange(start, end - start, tbend - tbstart); int origlen = end - start; if (mGapLength < 2) resizeFor(length() + 1); for (int i = mSpanCount - 1; i >= 0; i--) { if (mSpanStarts[i] == mGapStart) mSpanStarts[i]++; if (mSpanEnds[i] == mGapStart) mSpanEnds[i]++; } mText[mGapStart] = ' '; mGapStart++; mGapLength--; if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); int oldlen = (end + 1) - start; int inserted = change(false, start + 1, start + 1, tb, tbstart, tbend); change(false, start, start + 1, "", 0, 0); change(false, start + inserted, start + inserted + oldlen - 1, "", 0, 0); /* * Special case to keep the cursor in the same position * if it was somewhere in the middle of the replaced region. * If it was at the start or the end or crossing the whole * replacement, it should already be where it belongs. * TODO: Is there some more general mechanism that could * accomplish this? */ if (selstart > start && selstart < end) { long off = selstart - start; off = off * inserted / (end - start); selstart = (int) off + start; setSpan(false, Selection.SELECTION_START, selstart, selstart, Spanned.SPAN_POINT_POINT); } if (selend > start && selend < end) { long off = selend - start; off = off * inserted / (end - start); selend = (int) off + start; setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT); } sendTextChange(recipients, start, origlen, inserted); sendTextHasChanged(recipients); } return this; } /** * 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. */ public void setSpan(Object what, int start, int end, int flags) { setSpan(true, what, start, end, flags); } private void setSpan(boolean send, Object what, int start, int end, int flags) { int nstart = start; int nend = end; checkRange("setSpan", start, end); if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) { if (start != 0 && start != length()) { char c = charAt(start - 1); if (c != '\n') throw new RuntimeException( "PARAGRAPH span must start at paragraph boundary"); } } if ((flags & END_MASK) == PARAGRAPH) { if (end != 0 && end != length()) { char c = charAt(end - 1); if (c != '\n') throw new RuntimeException( "PARAGRAPH span must end at paragraph boundary"); } } if (start > mGapStart) start += mGapLength; else if (start == mGapStart) { int flag = (flags & START_MASK) >> START_SHIFT; if (flag == POINT || (flag == PARAGRAPH && start == length())) start += mGapLength; } if (end > mGapStart) end += mGapLength; else if (end == mGapStart) { int flag = (flags & END_MASK); if (flag == POINT || (flag == 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; } } if (mSpanCount + 1 >= mSpans.length) { int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1); Object[] newspans = new Object[newsize]; int[] newspanstarts = new int[newsize]; int[] newspanends = new int[newsize]; int[] newspanflags = new int[newsize]; System.arraycopy(mSpans, 0, newspans, 0, mSpanCount); System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount); System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount); System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount); mSpans = newspans; mSpanStarts = newspanstarts; mSpanEnds = newspanends; mSpanFlags = newspanflags; } mSpans[mSpanCount] = what; mSpanStarts[mSpanCount] = start; mSpanEnds[mSpanCount] = end; mSpanFlags[mSpanCount] = flags; mSpanCount++; if (send) sendSpanAdded(what, nstart, nend); } /** * Remove the specified markup object from the buffer. */ public void removeSpan(Object what) { for (int i = mSpanCount - 1; i >= 0; i--) { if (mSpans[i] == what) { int ostart = mSpanStarts[i]; int oend = mSpanEnds[i]; if (ostart > mGapStart) ostart -= mGapLength; if (oend > mGapStart) oend -= 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(what, ostart, oend); return; } } } /** * Return the buffer offset of the beginning of the specified * markup object, or -1 if it is not attached to this buffer. */ public int getSpanStart(Object what) { 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; } /** * Return the buffer offset of the end of the specified * markup object, or -1 if it is not attached to this buffer. */ public int getSpanEnd(Object what) { 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; } /** * Return the flags of the end of the specified * markup object, or 0 if it is not attached to this buffer. */ public int getSpanFlags(Object what) { int count = mSpanCount; Object[] spans = mSpans; for (int i = count - 1; i >= 0; i--) { if (spans[i] == what) { return mSpanFlags[i]; } } return 0; } /** * 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. */ public <T> T[] getSpans(int queryStart, int queryEnd, Class Other Android examples (source code examples)Here is a short list of links related to this Android SpannableStringBuilder.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.