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