Skip to content

Commit 33b7de8

Browse files
author
Gilles Debunne
committed
Multiple display lists for editable text
Bug 5763685 Long text in a ScrollView (not when the View's internal scroll is used) is cached as a unique display list when hardware rendering is on. As a result, each time the text is edited, the entire display list has to be updated, which takes a significant amount of time (up to 500ms for a few thousand lines), proportional to the size of the text. This CL splits the text into multiple display lists as the text is edited. The boundaries of the display list are aligned with paragraphs. There is still an issue when the number of lines changes: onLayout() is called which invalidates all the display list. When the source of that change is line wrapping and not a change in the view's dimensions, we should be able to simply shift down the previous DL instead of re-creating everything. Change-Id: I7de49a1e5637cdfc9ef06b64b1ec4b61d9ea2415
1 parent 58984b0 commit 33b7de8

File tree

2 files changed

+232
-31
lines changed

2 files changed

+232
-31
lines changed

core/java/android/text/DynamicLayout.java

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import android.text.style.UpdateLayout;
2121
import android.text.style.WrapTogetherSpan;
2222

23+
import com.android.internal.util.ArrayUtils;
24+
2325
import java.lang.ref.WeakReference;
2426

2527
/**
@@ -30,8 +32,7 @@
3032
* {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
3133
* Canvas.drawText()} directly.</p>
3234
*/
33-
public class DynamicLayout
34-
extends Layout
35+
public class DynamicLayout extends Layout
3536
{
3637
private static final int PRIORITY = 128;
3738

@@ -116,6 +117,10 @@ public DynamicLayout(CharSequence base, CharSequence display,
116117

117118
mObjects = new PackedObjectVector<Directions>(1);
118119

120+
mBlockEnds = new int[] { 0 };
121+
mBlockIndices = new int[] { INVALID_BLOCK_INDEX };
122+
mNumberOfBlocks = 1;
123+
119124
mIncludePad = includepad;
120125

121126
/*
@@ -295,9 +300,9 @@ private void reflow(CharSequence s, int where, int before, int after) {
295300
n--;
296301

297302
// remove affected lines from old layout
298-
299303
mInts.deleteAt(startline, endline - startline);
300304
mObjects.deleteAt(startline, endline - startline);
305+
updateBlocks(startline, endline - 1, n);
301306

302307
// adjust offsets in layout for new height and offsets
303308

@@ -363,6 +368,124 @@ private void reflow(CharSequence s, int where, int before, int after) {
363368
}
364369
}
365370

371+
/**
372+
* This method is called every time the layout is reflowed after an edition.
373+
* It updates the internal block data structure. The text is split in blocks
374+
* of contiguous lines, with at least one block for the entire text.
375+
* When a range of lines is edited, new blocks (from 0 to 3 depending on the
376+
* overlap structure) will replace the set of overlapping blocks.
377+
* Blocks are listed in order and are represented by their ending line number.
378+
* An index is associated to each block (which will be used by display lists),
379+
* this class simply invalidates the index of blocks overlapping a modification.
380+
*
381+
* @param startLine the first line of the range of modified lines
382+
* @param endLine the last line of the range, possibly equal to startLine, lower
383+
* than getLineCount()
384+
* @param newLineCount the number of lines that will replace the range, possibly 0
385+
*/
386+
private void updateBlocks(int startLine, int endLine, int newLineCount) {
387+
int firstBlock = -1;
388+
int lastBlock = -1;
389+
for (int i = 0; i < mNumberOfBlocks; i++) {
390+
if (mBlockEnds[i] >= startLine) {
391+
firstBlock = i;
392+
break;
393+
}
394+
}
395+
for (int i = firstBlock; i < mNumberOfBlocks; i++) {
396+
if (mBlockEnds[i] >= endLine) {
397+
lastBlock = i;
398+
break;
399+
}
400+
}
401+
final int lastBlockEndLine = mBlockEnds[lastBlock];
402+
403+
boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
404+
mBlockEnds[firstBlock - 1] + 1);
405+
boolean createBlock = newLineCount > 0;
406+
boolean createBlockAfter = endLine < mBlockEnds[lastBlock];
407+
408+
int numAddedBlocks = 0;
409+
if (createBlockBefore) numAddedBlocks++;
410+
if (createBlock) numAddedBlocks++;
411+
if (createBlockAfter) numAddedBlocks++;
412+
413+
final int numRemovedBlocks = lastBlock - firstBlock + 1;
414+
final int newNumberOfBlocks = mNumberOfBlocks + numAddedBlocks - numRemovedBlocks;
415+
416+
if (newNumberOfBlocks == 0) {
417+
// Even when text is empty, there is actually one line and hence one block
418+
mBlockEnds[0] = 0;
419+
mBlockIndices[0] = INVALID_BLOCK_INDEX;
420+
mNumberOfBlocks = 1;
421+
return;
422+
}
423+
424+
if (newNumberOfBlocks > mBlockEnds.length) {
425+
final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
426+
int[] blockEnds = new int[newSize];
427+
int[] blockIndices = new int[newSize];
428+
System.arraycopy(mBlockEnds, 0, blockEnds, 0, firstBlock);
429+
System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
430+
System.arraycopy(mBlockEnds, lastBlock + 1,
431+
blockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
432+
System.arraycopy(mBlockIndices, lastBlock + 1,
433+
blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
434+
mBlockEnds = blockEnds;
435+
mBlockIndices = blockIndices;
436+
} else {
437+
System.arraycopy(mBlockEnds, lastBlock + 1,
438+
mBlockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
439+
System.arraycopy(mBlockIndices, lastBlock + 1,
440+
mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
441+
}
442+
443+
mNumberOfBlocks = newNumberOfBlocks;
444+
final int deltaLines = newLineCount - (endLine - startLine + 1);
445+
for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
446+
mBlockEnds[i] += deltaLines;
447+
}
448+
449+
int blockIndex = firstBlock;
450+
if (createBlockBefore) {
451+
mBlockEnds[blockIndex] = startLine - 1;
452+
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
453+
blockIndex++;
454+
}
455+
456+
if (createBlock) {
457+
mBlockEnds[blockIndex] = startLine + newLineCount - 1;
458+
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
459+
blockIndex++;
460+
}
461+
462+
if (createBlockAfter) {
463+
mBlockEnds[blockIndex] = lastBlockEndLine + deltaLines;
464+
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
465+
}
466+
}
467+
468+
/**
469+
* @hide
470+
*/
471+
public int[] getBlockEnds() {
472+
return mBlockEnds;
473+
}
474+
475+
/**
476+
* @hide
477+
*/
478+
public int[] getBlockIndices() {
479+
return mBlockIndices;
480+
}
481+
482+
/**
483+
* @hide
484+
*/
485+
public int getNumberOfBlocks() {
486+
return mNumberOfBlocks;
487+
}
488+
366489
@Override
367490
public int getLineCount() {
368491
return mInts.size() - 1;
@@ -428,13 +551,15 @@ else if (s instanceof Spannable)
428551
}
429552

430553
public void beforeTextChanged(CharSequence s, int where, int before, int after) {
554+
// Intentionally empty
431555
}
432556

433557
public void onTextChanged(CharSequence s, int where, int before, int after) {
434558
reflow(s, where, before, after);
435559
}
436560

437561
public void afterTextChanged(Editable s) {
562+
// Intentionally empty
438563
}
439564

440565
public void onSpanAdded(Spannable s, Object o, int start, int end) {
@@ -486,6 +611,20 @@ public int getEllipsisCount(int line) {
486611
private PackedIntVector mInts;
487612
private PackedObjectVector<Directions> mObjects;
488613

614+
/*
615+
* Value used in mBlockIndices when a block has been created or recycled and indicating that its
616+
* display list needs to be re-created.
617+
* @hide
618+
*/
619+
public static final int INVALID_BLOCK_INDEX = -1;
620+
// Stores the line numbers of the last line of each block
621+
private int[] mBlockEnds;
622+
// The indices of this block's display list in TextView's internal display list array or
623+
// INVALID_BLOCK_INDEX if this block has been invalidated during an edition
624+
private int[] mBlockIndices;
625+
// Number of items actually currently being used in the above 2 arrays
626+
private int mNumberOfBlocks;
627+
489628
private int mTopPadding, mBottomPadding;
490629

491630
private static StaticLayout sStaticLayout = new StaticLayout(null);

0 commit comments

Comments
 (0)