DynamicLayoutpublic class DynamicLayout extends Layout DynamicLayout is a text layout that updates itself as the text is edited.
This is used by widgets to control text layout. You should not need
to use this class directly unless you are implementing your own widget
or custom display object, or need to call
{@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
Canvas.drawText()} directly. |
Fields Summary |
---|
private static final int | PRIORITY | private CharSequence | mBase | private CharSequence | mDisplay | private ChangeWatcher | mWatcher | private boolean | mIncludePad | private boolean | mEllipsize | private int | mEllipsizedWidth | private TextUtils.TruncateAt | mEllipsizeAt | private PackedIntVector | mInts | private PackedObjectVector | mObjects | private int | mTopPadding | private int | mBottomPadding | private static StaticLayout | sStaticLayout | private static Object | sLock | private static final int | START | private static final int | DIR | private static final int | TAB | private static final int | TOP | private static final int | DESCENT | private static final int | COLUMNS_NORMAL | private static final int | ELLIPSIS_START | private static final int | ELLIPSIS_COUNT | private static final int | COLUMNS_ELLIPSIZE | private static final int | START_MASK | private static final int | DIR_MASK | private static final int | DIR_SHIFT | private static final int | TAB_MASK | private static final int | ELLIPSIS_UNDEFINED |
Constructors Summary |
---|
public DynamicLayout(CharSequence base, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)Make a layout for the specified text that will be updated as
the text is changed.
this(base, base, paint, width, align, spacingmult, spacingadd,
includepad);
| public DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)Make a layout for the transformed text (password transformation
being the primary example of a transformation)
that will be updated as the base text is changed.
this(base, display, paint, width, align, spacingmult, spacingadd,
includepad, null, 0);
| public DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)Make a layout for the transformed text (password transformation
being the primary example of a transformation)
that will be updated as the base text is changed.
If ellipsize is non-null, the Layout will ellipsize the text
down to ellipsizedWidth.
super((ellipsize == null)
? display
: (display instanceof Spanned)
? new SpannedEllipsizer(display)
: new Ellipsizer(display),
paint, width, align, spacingmult, spacingadd);
mBase = base;
mDisplay = display;
if (ellipsize != null) {
mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
mEllipsizedWidth = ellipsizedWidth;
mEllipsizeAt = ellipsize;
} else {
mInts = new PackedIntVector(COLUMNS_NORMAL);
mEllipsizedWidth = width;
mEllipsizeAt = ellipsize;
}
mObjects = new PackedObjectVector<Directions>(1);
mIncludePad = includepad;
/*
* This is annoying, but we can't refer to the layout until
* superclass construction is finished, and the superclass
* constructor wants the reference to the display text.
*
* This will break if the superclass constructor ever actually
* cares about the content instead of just holding the reference.
*/
if (ellipsize != null) {
Ellipsizer e = (Ellipsizer) getText();
e.mLayout = this;
e.mWidth = ellipsizedWidth;
e.mMethod = ellipsize;
mEllipsize = true;
}
// Initial state is a single line with 0 characters (0 to 0),
// with top at 0 and bottom at whatever is natural, and
// undefined ellipsis.
int[] start;
if (ellipsize != null) {
start = new int[COLUMNS_ELLIPSIZE];
start[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
} else {
start = new int[COLUMNS_NORMAL];
}
Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int asc = fm.ascent;
int desc = fm.descent;
start[DIR] = DIR_LEFT_TO_RIGHT << DIR_SHIFT;
start[TOP] = 0;
start[DESCENT] = desc;
mInts.insertAt(0, start);
start[TOP] = desc - asc;
mInts.insertAt(1, start);
mObjects.insertAt(0, dirs);
// Update from 0 characters to whatever the real text is
reflow(base, 0, 0, base.length());
if (base instanceof Spannable) {
if (mWatcher == null)
mWatcher = new ChangeWatcher(this);
// Strip out any watchers for other DynamicLayouts.
Spannable sp = (Spannable) base;
ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
for (int i = 0; i < spans.length; i++)
sp.removeSpan(spans[i]);
sp.setSpan(mWatcher, 0, base.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE |
(PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
}
|
Methods Summary |
---|
private void | dump(boolean show)
int n = getLineCount();
for (int i = 0; i < n; i++) {
System.out.print("line " + i + ": " + getLineStart(i) + " to " + getLineEnd(i) + " ");
if (show) {
System.out.print(getText().subSequence(getLineStart(i),
getLineEnd(i)));
}
System.out.println("");
}
System.out.println("");
| public int | getBottomPadding()
return mBottomPadding;
| public int | getEllipsisCount(int line)
if (mEllipsizeAt == null) {
return 0;
}
return mInts.getValue(line, ELLIPSIS_COUNT);
| public int | getEllipsisStart(int line)
if (mEllipsizeAt == null) {
return 0;
}
return mInts.getValue(line, ELLIPSIS_START);
| public int | getEllipsizedWidth()
return mEllipsizedWidth;
| public boolean | getLineContainsTab(int line)
return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
| public int | getLineCount()
return mInts.size() - 1;
| public int | getLineDescent(int line)
return mInts.getValue(line, DESCENT);
| public final Directions | getLineDirections(int line)
return mObjects.getValue(line, 0);
| public int | getLineStart(int line)
return mInts.getValue(line, START) & START_MASK;
| public int | getLineTop(int line)
return mInts.getValue(line, TOP);
| public int | getParagraphDirection(int line)
return mInts.getValue(line, DIR) >> DIR_SHIFT;
| public int | getTopPadding()
return mTopPadding;
| private void | reflow(java.lang.CharSequence s, int where, int before, int after)
if (s != mBase)
return;
CharSequence text = mDisplay;
int len = text.length();
// seek back to the start of the paragraph
int find = TextUtils.lastIndexOf(text, '\n", where - 1);
if (find < 0)
find = 0;
else
find = find + 1;
{
int diff = where - find;
before += diff;
after += diff;
where -= diff;
}
// seek forward to the end of the paragraph
int look = TextUtils.indexOf(text, '\n", where + after);
if (look < 0)
look = len;
else
look++; // we want the index after the \n
int change = look - (where + after);
before += change;
after += change;
// seek further out to cover anything that is forced to wrap together
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
boolean again;
do {
again = false;
Object[] force = sp.getSpans(where, where + after,
WrapTogetherSpan.class);
for (int i = 0; i < force.length; i++) {
int st = sp.getSpanStart(force[i]);
int en = sp.getSpanEnd(force[i]);
if (st < where) {
again = true;
int diff = where - st;
before += diff;
after += diff;
where -= diff;
}
if (en > where + after) {
again = true;
int diff = en - (where + after);
before += diff;
after += diff;
}
}
} while (again);
}
// find affected region of old layout
int startline = getLineForOffset(where);
int startv = getLineTop(startline);
int endline = getLineForOffset(where + before);
if (where + after == len)
endline = getLineCount();
int endv = getLineTop(endline);
boolean islast = (endline == getLineCount());
// generate new layout for affected text
StaticLayout reflowed;
synchronized (sLock) {
reflowed = sStaticLayout;
sStaticLayout = null;
}
if (reflowed == null)
reflowed = new StaticLayout(true);
reflowed.generate(text, where, where + after,
getPaint(), getWidth(), getAlignment(),
getSpacingMultiplier(), getSpacingAdd(),
false, true, mEllipsize,
mEllipsizedWidth, mEllipsizeAt);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
// the very end of the buffer, then we already have a line that
// starts there, so disregard the blank line.
if (where + after != len &&
reflowed.getLineStart(n - 1) == where + after)
n--;
// remove affected lines from old layout
mInts.deleteAt(startline, endline - startline);
mObjects.deleteAt(startline, endline - startline);
// adjust offsets in layout for new height and offsets
int ht = reflowed.getLineTop(n);
int toppad = 0, botpad = 0;
if (mIncludePad && startline == 0) {
toppad = reflowed.getTopPadding();
mTopPadding = toppad;
ht -= toppad;
}
if (mIncludePad && islast) {
botpad = reflowed.getBottomPadding();
mBottomPadding = botpad;
ht += botpad;
}
mInts.adjustValuesBelow(startline, START, after - before);
mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
// insert new layout
int[] ints;
if (mEllipsize) {
ints = new int[COLUMNS_ELLIPSIZE];
ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
} else {
ints = new int[COLUMNS_NORMAL];
}
Directions[] objects = new Directions[1];
for (int i = 0; i < n; i++) {
ints[START] = reflowed.getLineStart(i) |
(reflowed.getParagraphDirection(i) << DIR_SHIFT) |
(reflowed.getLineContainsTab(i) ? TAB_MASK : 0);
int top = reflowed.getLineTop(i) + startv;
if (i > 0)
top -= toppad;
ints[TOP] = top;
int desc = reflowed.getLineDescent(i);
if (i == n - 1)
desc += botpad;
ints[DESCENT] = desc;
objects[0] = reflowed.getLineDirections(i);
if (mEllipsize) {
ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
}
mInts.insertAt(startline + i, ints);
mObjects.insertAt(startline + i, objects);
}
synchronized (sLock) {
sStaticLayout = reflowed;
}
|
|