FileDocCategorySizeDatePackage
DynamicLayout.javaAPI DocAndroid 1.5 API15924Wed May 06 22:41:56 BST 2009android.text

DynamicLayout

public 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 voiddump(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 intgetBottomPadding()

        return mBottomPadding;
    
public intgetEllipsisCount(int line)

        if (mEllipsizeAt == null) {
            return 0;
        }

        return mInts.getValue(line, ELLIPSIS_COUNT);
    
public intgetEllipsisStart(int line)

        if (mEllipsizeAt == null) {
            return 0;
        }

        return mInts.getValue(line, ELLIPSIS_START);
    
public intgetEllipsizedWidth()

        return mEllipsizedWidth;
    
public booleangetLineContainsTab(int line)

        return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
    
public intgetLineCount()

        return mInts.size() - 1;
    
public intgetLineDescent(int line)

        return mInts.getValue(line, DESCENT);
    
public final DirectionsgetLineDirections(int line)

        return mObjects.getValue(line, 0);
    
public intgetLineStart(int line)

        return mInts.getValue(line, START) & START_MASK;
    
public intgetLineTop(int line)

        return mInts.getValue(line, TOP);
    
public intgetParagraphDirection(int line)

        return mInts.getValue(line, DIR) >> DIR_SHIFT;
    
public intgetTopPadding()

        return mTopPadding;
    
private voidreflow(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;
        }