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 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 */ setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks)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 */ getBlockEndLines()553 public int[] getBlockEndLines() { 554 return mBlockEndLines; 555 } 556 557 /** 558 * @hide 559 */ getBlockIndices()560 public int[] getBlockIndices() { 561 return mBlockIndices; 562 } 563 564 /** 565 * @hide 566 */ getNumberOfBlocks()567 public int getNumberOfBlocks() { 568 return mNumberOfBlocks; 569 } 570 571 /** 572 * @hide 573 */ getIndexFirstChangedBlock()574 public int getIndexFirstChangedBlock() { 575 return mIndexFirstChangedBlock; 576 } 577 578 /** 579 * @hide 580 */ setIndexFirstChangedBlock(int i)581 public void setIndexFirstChangedBlock(int i) { 582 mIndexFirstChangedBlock = i; 583 } 584 585 @Override getLineCount()586 public int getLineCount() { 587 return mInts.size() - 1; 588 } 589 590 @Override getLineTop(int line)591 public int getLineTop(int line) { 592 return mInts.getValue(line, TOP); 593 } 594 595 @Override getLineDescent(int line)596 public int getLineDescent(int line) { 597 return mInts.getValue(line, DESCENT); 598 } 599 600 @Override getLineStart(int line)601 public int getLineStart(int line) { 602 return mInts.getValue(line, START) & START_MASK; 603 } 604 605 @Override getLineContainsTab(int line)606 public boolean getLineContainsTab(int line) { 607 return (mInts.getValue(line, TAB) & TAB_MASK) != 0; 608 } 609 610 @Override getParagraphDirection(int line)611 public int getParagraphDirection(int line) { 612 return mInts.getValue(line, DIR) >> DIR_SHIFT; 613 } 614 615 @Override getLineDirections(int line)616 public final Directions getLineDirections(int line) { 617 return mObjects.getValue(line, 0); 618 } 619 620 @Override getTopPadding()621 public int getTopPadding() { 622 return mTopPadding; 623 } 624 625 @Override getBottomPadding()626 public int getBottomPadding() { 627 return mBottomPadding; 628 } 629 630 @Override getEllipsizedWidth()631 public int getEllipsizedWidth() { 632 return mEllipsizedWidth; 633 } 634 635 private static class ChangeWatcher implements TextWatcher, SpanWatcher { ChangeWatcher(DynamicLayout layout)636 public ChangeWatcher(DynamicLayout layout) { 637 mLayout = new WeakReference<DynamicLayout>(layout); 638 } 639 reflow(CharSequence s, int where, int before, int after)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 beforeTextChanged(CharSequence s, int where, int before, int after)649 public void beforeTextChanged(CharSequence s, int where, int before, int after) { 650 // Intentionally empty 651 } 652 onTextChanged(CharSequence s, int where, int before, int after)653 public void onTextChanged(CharSequence s, int where, int before, int after) { 654 reflow(s, where, before, after); 655 } 656 afterTextChanged(Editable s)657 public void afterTextChanged(Editable s) { 658 // Intentionally empty 659 } 660 onSpanAdded(Spannable s, Object o, int start, int end)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 onSpanRemoved(Spannable s, Object o, int start, int end)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 onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend)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 getEllipsisStart(int line)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 getEllipsisCount(int line)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