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