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 */ DynamicLayout(CharSequence base, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)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 */ DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)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 */ DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 */ DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 reflow(CharSequence s, int where, int before, int after)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 */ createBlocks()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 */ addBlockAtOffset(int offset)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 */ updateBlocks(int startLine, int endLine, int newLineCount)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 final int deltaLines = newLineCount - (endLine - startLine + 1); 506 for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) { 507 mBlockEndLines[i] += deltaLines; 508 } 509 510 int blockIndex = firstBlock; 511 if (createBlockBefore) { 512 mBlockEndLines[blockIndex] = startLine - 1; 513 mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; 514 blockIndex++; 515 } 516 517 if (createBlock) { 518 mBlockEndLines[blockIndex] = startLine + newLineCount - 1; 519 mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; 520 blockIndex++; 521 } 522 523 if (createBlockAfter) { 524 mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines; 525 mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; 526 } 527 } 528 529 /** 530 * This package private method is used for test purposes only 531 * @hide 532 */ setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks)533 void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) { 534 mBlockEndLines = new int[blockEndLines.length]; 535 mBlockIndices = new int[blockIndices.length]; 536 System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length); 537 System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length); 538 mNumberOfBlocks = numberOfBlocks; 539 } 540 541 /** 542 * @hide 543 */ getBlockEndLines()544 public int[] getBlockEndLines() { 545 return mBlockEndLines; 546 } 547 548 /** 549 * @hide 550 */ getBlockIndices()551 public int[] getBlockIndices() { 552 return mBlockIndices; 553 } 554 555 /** 556 * @hide 557 */ getNumberOfBlocks()558 public int getNumberOfBlocks() { 559 return mNumberOfBlocks; 560 } 561 562 @Override getLineCount()563 public int getLineCount() { 564 return mInts.size() - 1; 565 } 566 567 @Override getLineTop(int line)568 public int getLineTop(int line) { 569 return mInts.getValue(line, TOP); 570 } 571 572 @Override getLineDescent(int line)573 public int getLineDescent(int line) { 574 return mInts.getValue(line, DESCENT); 575 } 576 577 @Override getLineStart(int line)578 public int getLineStart(int line) { 579 return mInts.getValue(line, START) & START_MASK; 580 } 581 582 @Override getLineContainsTab(int line)583 public boolean getLineContainsTab(int line) { 584 return (mInts.getValue(line, TAB) & TAB_MASK) != 0; 585 } 586 587 @Override getParagraphDirection(int line)588 public int getParagraphDirection(int line) { 589 return mInts.getValue(line, DIR) >> DIR_SHIFT; 590 } 591 592 @Override getLineDirections(int line)593 public final Directions getLineDirections(int line) { 594 return mObjects.getValue(line, 0); 595 } 596 597 @Override getTopPadding()598 public int getTopPadding() { 599 return mTopPadding; 600 } 601 602 @Override getBottomPadding()603 public int getBottomPadding() { 604 return mBottomPadding; 605 } 606 607 @Override getEllipsizedWidth()608 public int getEllipsizedWidth() { 609 return mEllipsizedWidth; 610 } 611 612 private static class ChangeWatcher implements TextWatcher, SpanWatcher { ChangeWatcher(DynamicLayout layout)613 public ChangeWatcher(DynamicLayout layout) { 614 mLayout = new WeakReference<DynamicLayout>(layout); 615 } 616 reflow(CharSequence s, int where, int before, int after)617 private void reflow(CharSequence s, int where, int before, int after) { 618 DynamicLayout ml = mLayout.get(); 619 620 if (ml != null) 621 ml.reflow(s, where, before, after); 622 else if (s instanceof Spannable) 623 ((Spannable) s).removeSpan(this); 624 } 625 beforeTextChanged(CharSequence s, int where, int before, int after)626 public void beforeTextChanged(CharSequence s, int where, int before, int after) { 627 // Intentionally empty 628 } 629 onTextChanged(CharSequence s, int where, int before, int after)630 public void onTextChanged(CharSequence s, int where, int before, int after) { 631 reflow(s, where, before, after); 632 } 633 afterTextChanged(Editable s)634 public void afterTextChanged(Editable s) { 635 // Intentionally empty 636 } 637 onSpanAdded(Spannable s, Object o, int start, int end)638 public void onSpanAdded(Spannable s, Object o, int start, int end) { 639 if (o instanceof UpdateLayout) 640 reflow(s, start, end - start, end - start); 641 } 642 onSpanRemoved(Spannable s, Object o, int start, int end)643 public void onSpanRemoved(Spannable s, Object o, int start, int end) { 644 if (o instanceof UpdateLayout) 645 reflow(s, start, end - start, end - start); 646 } 647 onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend)648 public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) { 649 if (o instanceof UpdateLayout) { 650 reflow(s, start, end - start, end - start); 651 reflow(s, nstart, nend - nstart, nend - nstart); 652 } 653 } 654 655 private WeakReference<DynamicLayout> mLayout; 656 } 657 658 @Override getEllipsisStart(int line)659 public int getEllipsisStart(int line) { 660 if (mEllipsizeAt == null) { 661 return 0; 662 } 663 664 return mInts.getValue(line, ELLIPSIS_START); 665 } 666 667 @Override getEllipsisCount(int line)668 public int getEllipsisCount(int line) { 669 if (mEllipsizeAt == null) { 670 return 0; 671 } 672 673 return mInts.getValue(line, ELLIPSIS_COUNT); 674 } 675 676 private CharSequence mBase; 677 private CharSequence mDisplay; 678 private ChangeWatcher mWatcher; 679 private boolean mIncludePad; 680 private boolean mEllipsize; 681 private int mEllipsizedWidth; 682 private TextUtils.TruncateAt mEllipsizeAt; 683 684 private PackedIntVector mInts; 685 private PackedObjectVector<Directions> mObjects; 686 687 /** 688 * Value used in mBlockIndices when a block has been created or recycled and indicating that its 689 * display list needs to be re-created. 690 * @hide 691 */ 692 public static final int INVALID_BLOCK_INDEX = -1; 693 // Stores the line numbers of the last line of each block (inclusive) 694 private int[] mBlockEndLines; 695 // The indices of this block's display list in TextView's internal display list array or 696 // INVALID_BLOCK_INDEX if this block has been invalidated during an edition 697 private int[] mBlockIndices; 698 // Number of items actually currently being used in the above 2 arrays 699 private int mNumberOfBlocks; 700 701 private int mTopPadding, mBottomPadding; 702 703 private static StaticLayout sStaticLayout = new StaticLayout(null); 704 705 private static final Object[] sLock = new Object[0]; 706 707 private static final int START = 0; 708 private static final int DIR = START; 709 private static final int TAB = START; 710 private static final int TOP = 1; 711 private static final int DESCENT = 2; 712 private static final int COLUMNS_NORMAL = 3; 713 714 private static final int ELLIPSIS_START = 3; 715 private static final int ELLIPSIS_COUNT = 4; 716 private static final int COLUMNS_ELLIPSIZE = 5; 717 718 private static final int START_MASK = 0x1FFFFFFF; 719 private static final int DIR_SHIFT = 30; 720 private static final int TAB_MASK = 0x20000000; 721 722 private static final int ELLIPSIS_UNDEFINED = 0x80000000; 723 } 724