Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text;
     18 
     19 import android.graphics.Paint;
     20 import android.text.style.UpdateLayout;
     21 import android.text.style.WrapTogetherSpan;
     22 
     23 import com.android.internal.util.ArrayUtils;
     24 
     25 import java.lang.ref.WeakReference;
     26 
     27 /**
     28  * DynamicLayout is a text layout that updates itself as the text is edited.
     29  * <p>This is used by widgets to control text layout. You should not need
     30  * to use this class directly unless you are implementing your own widget
     31  * or custom display object, or need to call
     32  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
     33  *  Canvas.drawText()} directly.</p>
     34  */
     35 public class DynamicLayout extends Layout
     36 {
     37     private static final int PRIORITY = 128;
     38     private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
     39 
     40     /**
     41      * Make a layout for the specified text that will be updated as
     42      * the text is changed.
     43      */
     44     public DynamicLayout(CharSequence base,
     45                          TextPaint paint,
     46                          int width, Alignment align,
     47                          float spacingmult, float spacingadd,
     48                          boolean includepad) {
     49         this(base, base, paint, width, align, spacingmult, spacingadd,
     50              includepad);
     51     }
     52 
     53     /**
     54      * Make a layout for the transformed text (password transformation
     55      * being the primary example of a transformation)
     56      * that will be updated as the base text is changed.
     57      */
     58     public DynamicLayout(CharSequence base, CharSequence display,
     59                          TextPaint paint,
     60                          int width, Alignment align,
     61                          float spacingmult, float spacingadd,
     62                          boolean includepad) {
     63         this(base, display, paint, width, align, spacingmult, spacingadd,
     64              includepad, null, 0);
     65     }
     66 
     67     /**
     68      * Make a layout for the transformed text (password transformation
     69      * being the primary example of a transformation)
     70      * that will be updated as the base text is changed.
     71      * If ellipsize is non-null, the Layout will ellipsize the text
     72      * down to ellipsizedWidth.
     73      */
     74     public DynamicLayout(CharSequence base, CharSequence display,
     75                          TextPaint paint,
     76                          int width, Alignment align,
     77                          float spacingmult, float spacingadd,
     78                          boolean includepad,
     79                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
     80         this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
     81                 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
     82     }
     83 
     84     /**
     85      * Make a layout for the transformed text (password transformation
     86      * being the primary example of a transformation)
     87      * that will be updated as the base text is changed.
     88      * If ellipsize is non-null, the Layout will ellipsize the text
     89      * down to ellipsizedWidth.
     90      * *
     91      * *@hide
     92      */
     93     public DynamicLayout(CharSequence base, CharSequence display,
     94                          TextPaint paint,
     95                          int width, Alignment align, TextDirectionHeuristic textDir,
     96                          float spacingmult, float spacingadd,
     97                          boolean includepad,
     98                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
     99         super((ellipsize == null)
    100                 ? display
    101                 : (display instanceof Spanned)
    102                     ? new SpannedEllipsizer(display)
    103                     : new Ellipsizer(display),
    104               paint, width, align, textDir, spacingmult, spacingadd);
    105 
    106         mBase = base;
    107         mDisplay = display;
    108 
    109         if (ellipsize != null) {
    110             mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
    111             mEllipsizedWidth = ellipsizedWidth;
    112             mEllipsizeAt = ellipsize;
    113         } else {
    114             mInts = new PackedIntVector(COLUMNS_NORMAL);
    115             mEllipsizedWidth = width;
    116             mEllipsizeAt = null;
    117         }
    118 
    119         mObjects = new PackedObjectVector<Directions>(1);
    120 
    121         mIncludePad = includepad;
    122 
    123         /*
    124          * This is annoying, but we can't refer to the layout until
    125          * superclass construction is finished, and the superclass
    126          * constructor wants the reference to the display text.
    127          *
    128          * This will break if the superclass constructor ever actually
    129          * cares about the content instead of just holding the reference.
    130          */
    131         if (ellipsize != null) {
    132             Ellipsizer e = (Ellipsizer) getText();
    133 
    134             e.mLayout = this;
    135             e.mWidth = ellipsizedWidth;
    136             e.mMethod = ellipsize;
    137             mEllipsize = true;
    138         }
    139 
    140         // Initial state is a single line with 0 characters (0 to 0),
    141         // with top at 0 and bottom at whatever is natural, and
    142         // undefined ellipsis.
    143 
    144         int[] start;
    145 
    146         if (ellipsize != null) {
    147             start = new int[COLUMNS_ELLIPSIZE];
    148             start[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
    149         } else {
    150             start = new int[COLUMNS_NORMAL];
    151         }
    152 
    153         Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
    154 
    155         Paint.FontMetricsInt fm = paint.getFontMetricsInt();
    156         int asc = fm.ascent;
    157         int desc = fm.descent;
    158 
    159         start[DIR] = DIR_LEFT_TO_RIGHT << DIR_SHIFT;
    160         start[TOP] = 0;
    161         start[DESCENT] = desc;
    162         mInts.insertAt(0, start);
    163 
    164         start[TOP] = desc - asc;
    165         mInts.insertAt(1, start);
    166 
    167         mObjects.insertAt(0, dirs);
    168 
    169         // Update from 0 characters to whatever the real text is
    170         reflow(base, 0, 0, base.length());
    171 
    172         if (base instanceof Spannable) {
    173             if (mWatcher == null)
    174                 mWatcher = new ChangeWatcher(this);
    175 
    176             // Strip out any watchers for other DynamicLayouts.
    177             Spannable sp = (Spannable) base;
    178             ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
    179             for (int i = 0; i < spans.length; i++)
    180                 sp.removeSpan(spans[i]);
    181 
    182             sp.setSpan(mWatcher, 0, base.length(),
    183                        Spannable.SPAN_INCLUSIVE_INCLUSIVE |
    184                        (PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
    185         }
    186     }
    187 
    188     private void reflow(CharSequence s, int where, int before, int after) {
    189         if (s != mBase)
    190             return;
    191 
    192         CharSequence text = mDisplay;
    193         int len = text.length();
    194 
    195         // seek back to the start of the paragraph
    196 
    197         int find = TextUtils.lastIndexOf(text, '\n', where - 1);
    198         if (find < 0)
    199             find = 0;
    200         else
    201             find = find + 1;
    202 
    203         {
    204             int diff = where - find;
    205             before += diff;
    206             after += diff;
    207             where -= diff;
    208         }
    209 
    210         // seek forward to the end of the paragraph
    211 
    212         int look = TextUtils.indexOf(text, '\n', where + after);
    213         if (look < 0)
    214             look = len;
    215         else
    216             look++; // we want the index after the \n
    217 
    218         int change = look - (where + after);
    219         before += change;
    220         after += change;
    221 
    222         // seek further out to cover anything that is forced to wrap together
    223 
    224         if (text instanceof Spanned) {
    225             Spanned sp = (Spanned) text;
    226             boolean again;
    227 
    228             do {
    229                 again = false;
    230 
    231                 Object[] force = sp.getSpans(where, where + after,
    232                                              WrapTogetherSpan.class);
    233 
    234                 for (int i = 0; i < force.length; i++) {
    235                     int st = sp.getSpanStart(force[i]);
    236                     int en = sp.getSpanEnd(force[i]);
    237 
    238                     if (st < where) {
    239                         again = true;
    240 
    241                         int diff = where - st;
    242                         before += diff;
    243                         after += diff;
    244                         where -= diff;
    245                     }
    246 
    247                     if (en > where + after) {
    248                         again = true;
    249 
    250                         int diff = en - (where + after);
    251                         before += diff;
    252                         after += diff;
    253                     }
    254                 }
    255             } while (again);
    256         }
    257 
    258         // find affected region of old layout
    259 
    260         int startline = getLineForOffset(where);
    261         int startv = getLineTop(startline);
    262 
    263         int endline = getLineForOffset(where + before);
    264         if (where + after == len)
    265             endline = getLineCount();
    266         int endv = getLineTop(endline);
    267         boolean islast = (endline == getLineCount());
    268 
    269         // generate new layout for affected text
    270 
    271         StaticLayout reflowed;
    272 
    273         synchronized (sLock) {
    274             reflowed = sStaticLayout;
    275             sStaticLayout = null;
    276         }
    277 
    278         if (reflowed == null) {
    279             reflowed = new StaticLayout(null);
    280         } else {
    281             reflowed.prepare();
    282         }
    283 
    284         reflowed.generate(text, where, where + after,
    285                 getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
    286                 getSpacingAdd(), false,
    287                 true, mEllipsizedWidth, mEllipsizeAt);
    288         int n = reflowed.getLineCount();
    289 
    290         // If the new layout has a blank line at the end, but it is not
    291         // the very end of the buffer, then we already have a line that
    292         // starts there, so disregard the blank line.
    293 
    294         if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
    295             n--;
    296 
    297         // remove affected lines from old layout
    298         mInts.deleteAt(startline, endline - startline);
    299         mObjects.deleteAt(startline, endline - startline);
    300 
    301         // adjust offsets in layout for new height and offsets
    302 
    303         int ht = reflowed.getLineTop(n);
    304         int toppad = 0, botpad = 0;
    305 
    306         if (mIncludePad && startline == 0) {
    307             toppad = reflowed.getTopPadding();
    308             mTopPadding = toppad;
    309             ht -= toppad;
    310         }
    311         if (mIncludePad && islast) {
    312             botpad = reflowed.getBottomPadding();
    313             mBottomPadding = botpad;
    314             ht += botpad;
    315         }
    316 
    317         mInts.adjustValuesBelow(startline, START, after - before);
    318         mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
    319 
    320         // insert new layout
    321 
    322         int[] ints;
    323 
    324         if (mEllipsize) {
    325             ints = new int[COLUMNS_ELLIPSIZE];
    326             ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
    327         } else {
    328             ints = new int[COLUMNS_NORMAL];
    329         }
    330 
    331         Directions[] objects = new Directions[1];
    332 
    333         for (int i = 0; i < n; i++) {
    334             ints[START] = reflowed.getLineStart(i) |
    335                           (reflowed.getParagraphDirection(i) << DIR_SHIFT) |
    336                           (reflowed.getLineContainsTab(i) ? TAB_MASK : 0);
    337 
    338             int top = reflowed.getLineTop(i) + startv;
    339             if (i > 0)
    340                 top -= toppad;
    341             ints[TOP] = top;
    342 
    343             int desc = reflowed.getLineDescent(i);
    344             if (i == n - 1)
    345                 desc += botpad;
    346 
    347             ints[DESCENT] = desc;
    348             objects[0] = reflowed.getLineDirections(i);
    349 
    350             if (mEllipsize) {
    351                 ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
    352                 ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
    353             }
    354 
    355             mInts.insertAt(startline + i, ints);
    356             mObjects.insertAt(startline + i, objects);
    357         }
    358 
    359         updateBlocks(startline, endline - 1, n);
    360 
    361         synchronized (sLock) {
    362             sStaticLayout = reflowed;
    363             reflowed.finish();
    364         }
    365     }
    366 
    367     /**
    368      * Create the initial block structure, cutting the text into blocks of at least
    369      * BLOCK_MINIMUM_CHARACTER_SIZE characters, aligned on the ends of paragraphs.
    370      */
    371     private void createBlocks() {
    372         int offset = BLOCK_MINIMUM_CHARACTER_LENGTH;
    373         mNumberOfBlocks = 0;
    374         final CharSequence text = mDisplay;
    375 
    376         while (true) {
    377             offset = TextUtils.indexOf(text, '\n', offset);
    378             if (offset < 0) {
    379                 addBlockAtOffset(text.length());
    380                 break;
    381             } else {
    382                 addBlockAtOffset(offset);
    383                 offset += BLOCK_MINIMUM_CHARACTER_LENGTH;
    384             }
    385         }
    386 
    387         // mBlockIndices and mBlockEndLines should have the same length
    388         mBlockIndices = new int[mBlockEndLines.length];
    389         for (int i = 0; i < mBlockEndLines.length; i++) {
    390             mBlockIndices[i] = INVALID_BLOCK_INDEX;
    391         }
    392     }
    393 
    394     /**
    395      * Create a new block, ending at the specified character offset.
    396      * A block will actually be created only if has at least one line, i.e. this offset is
    397      * not on the end line of the previous block.
    398      */
    399     private void addBlockAtOffset(int offset) {
    400         final int line = getLineForOffset(offset);
    401 
    402         if (mBlockEndLines == null) {
    403             // Initial creation of the array, no test on previous block ending line
    404             mBlockEndLines = new int[ArrayUtils.idealIntArraySize(1)];
    405             mBlockEndLines[mNumberOfBlocks] = line;
    406             mNumberOfBlocks++;
    407             return;
    408         }
    409 
    410         final int previousBlockEndLine = mBlockEndLines[mNumberOfBlocks - 1];
    411         if (line > previousBlockEndLine) {
    412             if (mNumberOfBlocks == mBlockEndLines.length) {
    413                 // Grow the array if needed
    414                 int[] blockEndLines = new int[ArrayUtils.idealIntArraySize(mNumberOfBlocks + 1)];
    415                 System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, mNumberOfBlocks);
    416                 mBlockEndLines = blockEndLines;
    417             }
    418             mBlockEndLines[mNumberOfBlocks] = line;
    419             mNumberOfBlocks++;
    420         }
    421     }
    422 
    423     /**
    424      * This method is called every time the layout is reflowed after an edition.
    425      * It updates the internal block data structure. The text is split in blocks
    426      * of contiguous lines, with at least one block for the entire text.
    427      * When a range of lines is edited, new blocks (from 0 to 3 depending on the
    428      * overlap structure) will replace the set of overlapping blocks.
    429      * Blocks are listed in order and are represented by their ending line number.
    430      * An index is associated to each block (which will be used by display lists),
    431      * this class simply invalidates the index of blocks overlapping a modification.
    432      *
    433      * This method is package private and not private so that it can be tested.
    434      *
    435      * @param startLine the first line of the range of modified lines
    436      * @param endLine the last line of the range, possibly equal to startLine, lower
    437      * than getLineCount()
    438      * @param newLineCount the number of lines that will replace the range, possibly 0
    439      *
    440      * @hide
    441      */
    442     void updateBlocks(int startLine, int endLine, int newLineCount) {
    443         if (mBlockEndLines == null) {
    444             createBlocks();
    445             return;
    446         }
    447 
    448         int firstBlock = -1;
    449         int lastBlock = -1;
    450         for (int i = 0; i < mNumberOfBlocks; i++) {
    451             if (mBlockEndLines[i] >= startLine) {
    452                 firstBlock = i;
    453                 break;
    454             }
    455         }
    456         for (int i = firstBlock; i < mNumberOfBlocks; i++) {
    457             if (mBlockEndLines[i] >= endLine) {
    458                 lastBlock = i;
    459                 break;
    460             }
    461         }
    462         final int lastBlockEndLine = mBlockEndLines[lastBlock];
    463 
    464         boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
    465                 mBlockEndLines[firstBlock - 1] + 1);
    466         boolean createBlock = newLineCount > 0;
    467         boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
    468 
    469         int numAddedBlocks = 0;
    470         if (createBlockBefore) numAddedBlocks++;
    471         if (createBlock) numAddedBlocks++;
    472         if (createBlockAfter) numAddedBlocks++;
    473 
    474         final int numRemovedBlocks = lastBlock - firstBlock + 1;
    475         final int newNumberOfBlocks = mNumberOfBlocks + numAddedBlocks - numRemovedBlocks;
    476 
    477         if (newNumberOfBlocks == 0) {
    478             // Even when text is empty, there is actually one line and hence one block
    479             mBlockEndLines[0] = 0;
    480             mBlockIndices[0] = INVALID_BLOCK_INDEX;
    481             mNumberOfBlocks = 1;
    482             return;
    483         }
    484 
    485         if (newNumberOfBlocks > mBlockEndLines.length) {
    486             final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
    487             int[] blockEndLines = new int[newSize];
    488             int[] blockIndices = new int[newSize];
    489             System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, firstBlock);
    490             System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
    491             System.arraycopy(mBlockEndLines, lastBlock + 1,
    492                     blockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    493             System.arraycopy(mBlockIndices, lastBlock + 1,
    494                     blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    495             mBlockEndLines = blockEndLines;
    496             mBlockIndices = blockIndices;
    497         } else {
    498             System.arraycopy(mBlockEndLines, lastBlock + 1,
    499                     mBlockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    500             System.arraycopy(mBlockIndices, lastBlock + 1,
    501                     mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    502         }
    503 
    504         mNumberOfBlocks = newNumberOfBlocks;
    505         int newFirstChangedBlock;
    506         final int deltaLines = newLineCount - (endLine - startLine + 1);
    507         if (deltaLines != 0) {
    508             // Display list whose index is >= mIndexFirstChangedBlock is valid
    509             // but it needs to update its drawing location.
    510             newFirstChangedBlock = firstBlock + numAddedBlocks;
    511             for (int i = newFirstChangedBlock; i < mNumberOfBlocks; i++) {
    512                 mBlockEndLines[i] += deltaLines;
    513             }
    514         } else {
    515             newFirstChangedBlock = mNumberOfBlocks;
    516         }
    517         mIndexFirstChangedBlock = Math.min(mIndexFirstChangedBlock, newFirstChangedBlock);
    518 
    519         int blockIndex = firstBlock;
    520         if (createBlockBefore) {
    521             mBlockEndLines[blockIndex] = startLine - 1;
    522             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    523             blockIndex++;
    524         }
    525 
    526         if (createBlock) {
    527             mBlockEndLines[blockIndex] = startLine + newLineCount - 1;
    528             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    529             blockIndex++;
    530         }
    531 
    532         if (createBlockAfter) {
    533             mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines;
    534             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    535         }
    536     }
    537 
    538     /**
    539      * This package private method is used for test purposes only
    540      * @hide
    541      */
    542     void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) {
    543         mBlockEndLines = new int[blockEndLines.length];
    544         mBlockIndices = new int[blockIndices.length];
    545         System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length);
    546         System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length);
    547         mNumberOfBlocks = numberOfBlocks;
    548     }
    549 
    550     /**
    551      * @hide
    552      */
    553     public int[] getBlockEndLines() {
    554         return mBlockEndLines;
    555     }
    556 
    557     /**
    558      * @hide
    559      */
    560     public int[] getBlockIndices() {
    561         return mBlockIndices;
    562     }
    563 
    564     /**
    565      * @hide
    566      */
    567     public int getNumberOfBlocks() {
    568         return mNumberOfBlocks;
    569     }
    570 
    571     /**
    572      * @hide
    573      */
    574     public int getIndexFirstChangedBlock() {
    575         return mIndexFirstChangedBlock;
    576     }
    577 
    578     /**
    579      * @hide
    580      */
    581     public void setIndexFirstChangedBlock(int i) {
    582         mIndexFirstChangedBlock = i;
    583     }
    584 
    585     @Override
    586     public int getLineCount() {
    587         return mInts.size() - 1;
    588     }
    589 
    590     @Override
    591     public int getLineTop(int line) {
    592         return mInts.getValue(line, TOP);
    593     }
    594 
    595     @Override
    596     public int getLineDescent(int line) {
    597         return mInts.getValue(line, DESCENT);
    598     }
    599 
    600     @Override
    601     public int getLineStart(int line) {
    602         return mInts.getValue(line, START) & START_MASK;
    603     }
    604 
    605     @Override
    606     public boolean getLineContainsTab(int line) {
    607         return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
    608     }
    609 
    610     @Override
    611     public int getParagraphDirection(int line) {
    612         return mInts.getValue(line, DIR) >> DIR_SHIFT;
    613     }
    614 
    615     @Override
    616     public final Directions getLineDirections(int line) {
    617         return mObjects.getValue(line, 0);
    618     }
    619 
    620     @Override
    621     public int getTopPadding() {
    622         return mTopPadding;
    623     }
    624 
    625     @Override
    626     public int getBottomPadding() {
    627         return mBottomPadding;
    628     }
    629 
    630     @Override
    631     public int getEllipsizedWidth() {
    632         return mEllipsizedWidth;
    633     }
    634 
    635     private static class ChangeWatcher implements TextWatcher, SpanWatcher {
    636         public ChangeWatcher(DynamicLayout layout) {
    637             mLayout = new WeakReference<DynamicLayout>(layout);
    638         }
    639 
    640         private void reflow(CharSequence s, int where, int before, int after) {
    641             DynamicLayout ml = mLayout.get();
    642 
    643             if (ml != null)
    644                 ml.reflow(s, where, before, after);
    645             else if (s instanceof Spannable)
    646                 ((Spannable) s).removeSpan(this);
    647         }
    648 
    649         public void beforeTextChanged(CharSequence s, int where, int before, int after) {
    650             // Intentionally empty
    651         }
    652 
    653         public void onTextChanged(CharSequence s, int where, int before, int after) {
    654             reflow(s, where, before, after);
    655         }
    656 
    657         public void afterTextChanged(Editable s) {
    658             // Intentionally empty
    659         }
    660 
    661         public void onSpanAdded(Spannable s, Object o, int start, int end) {
    662             if (o instanceof UpdateLayout)
    663                 reflow(s, start, end - start, end - start);
    664         }
    665 
    666         public void onSpanRemoved(Spannable s, Object o, int start, int end) {
    667             if (o instanceof UpdateLayout)
    668                 reflow(s, start, end - start, end - start);
    669         }
    670 
    671         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
    672             if (o instanceof UpdateLayout) {
    673                 reflow(s, start, end - start, end - start);
    674                 reflow(s, nstart, nend - nstart, nend - nstart);
    675             }
    676         }
    677 
    678         private WeakReference<DynamicLayout> mLayout;
    679     }
    680 
    681     @Override
    682     public int getEllipsisStart(int line) {
    683         if (mEllipsizeAt == null) {
    684             return 0;
    685         }
    686 
    687         return mInts.getValue(line, ELLIPSIS_START);
    688     }
    689 
    690     @Override
    691     public int getEllipsisCount(int line) {
    692         if (mEllipsizeAt == null) {
    693             return 0;
    694         }
    695 
    696         return mInts.getValue(line, ELLIPSIS_COUNT);
    697     }
    698 
    699     private CharSequence mBase;
    700     private CharSequence mDisplay;
    701     private ChangeWatcher mWatcher;
    702     private boolean mIncludePad;
    703     private boolean mEllipsize;
    704     private int mEllipsizedWidth;
    705     private TextUtils.TruncateAt mEllipsizeAt;
    706 
    707     private PackedIntVector mInts;
    708     private PackedObjectVector<Directions> mObjects;
    709 
    710     /**
    711      * Value used in mBlockIndices when a block has been created or recycled and indicating that its
    712      * display list needs to be re-created.
    713      * @hide
    714      */
    715     public static final int INVALID_BLOCK_INDEX = -1;
    716     // Stores the line numbers of the last line of each block (inclusive)
    717     private int[] mBlockEndLines;
    718     // The indices of this block's display list in TextView's internal display list array or
    719     // INVALID_BLOCK_INDEX if this block has been invalidated during an edition
    720     private int[] mBlockIndices;
    721     // Number of items actually currently being used in the above 2 arrays
    722     private int mNumberOfBlocks;
    723     // The first index of the blocks whose locations are changed
    724     private int mIndexFirstChangedBlock;
    725 
    726     private int mTopPadding, mBottomPadding;
    727 
    728     private static StaticLayout sStaticLayout = new StaticLayout(null);
    729 
    730     private static final Object[] sLock = new Object[0];
    731 
    732     private static final int START = 0;
    733     private static final int DIR = START;
    734     private static final int TAB = START;
    735     private static final int TOP = 1;
    736     private static final int DESCENT = 2;
    737     private static final int COLUMNS_NORMAL = 3;
    738 
    739     private static final int ELLIPSIS_START = 3;
    740     private static final int ELLIPSIS_COUNT = 4;
    741     private static final int COLUMNS_ELLIPSIZE = 5;
    742 
    743     private static final int START_MASK = 0x1FFFFFFF;
    744     private static final int DIR_SHIFT  = 30;
    745     private static final int TAB_MASK   = 0x20000000;
    746 
    747     private static final int ELLIPSIS_UNDEFINED = 0x80000000;
    748 }
    749