Lazy RenderNode creation to improve performance.
Bug: 27889485
Change-Id: I63af3a6ecbf921cb428e5f7812e1c6983bfb0882
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0447a6..b30b7f7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -176,11 +176,21 @@
InputMethodState mInputMethodState;
private static class TextRenderNode {
+ // Render node has 3 recording states:
+ // 1. Recorded operations are valid.
+ // #needsRecord() returns false, but needsToBeShifted is false.
+ // 2. Recorded operations are not valid, but just the position needed to be updated.
+ // #needsRecord() returns false, but needsToBeShifted is true.
+ // 3. Recorded operations are not valid. Need to record operations. #needsRecord() returns
+ // true.
RenderNode renderNode;
boolean isDirty;
+ // Becomes true when recorded operations can be reused, but the position has to be updated.
+ boolean needsToBeShifted;
public TextRenderNode(String name) {
- isDirty = true;
renderNode = RenderNode.create(name, null);
+ isDirty = true;
+ needsToBeShifted = true;
}
boolean needsRecord() {
return isDirty || !renderNode.isValid();
@@ -1686,85 +1696,113 @@
final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
final int indexFirstChangedBlock = dynamicLayout.getIndexFirstChangedBlock();
- int endOfPreviousBlock = -1;
- int searchStartIndex = 0;
- for (int i = 0; i < numberOfBlocks; i++) {
- int blockEndLine = blockEndLines[i];
- int blockIndex = blockIndices[i];
+ int startBlock = Arrays.binarySearch(blockEndLines, 0, numberOfBlocks, firstLine);
+ if (startBlock < 0) {
+ startBlock = -(startBlock + 1);
+ }
+ startBlock = Math.min(indexFirstChangedBlock, startBlock);
- final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
- if (blockIsInvalid) {
- blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
- searchStartIndex);
- // Note how dynamic layout's internal block indices get updated from Editor
- blockIndices[i] = blockIndex;
- if (mTextRenderNodes[blockIndex] != null) {
- mTextRenderNodes[blockIndex].isDirty = true;
- }
- searchStartIndex = blockIndex + 1;
+ int startIndexToFindAvailableRenderNode = 0;
+ int lastIndex = numberOfBlocks;
+
+ for (int i = startBlock; i < numberOfBlocks; i++) {
+ final int blockIndex = blockIndices[i];
+ if (i >= indexFirstChangedBlock
+ && blockIndex != DynamicLayout.INVALID_BLOCK_INDEX
+ && mTextRenderNodes[blockIndex] != null) {
+ mTextRenderNodes[blockIndex].needsToBeShifted = true;
}
-
- if (mTextRenderNodes[blockIndex] == null) {
- mTextRenderNodes[blockIndex] =
- new TextRenderNode("Text " + blockIndex);
+ if (blockEndLines[i] < firstLine) {
+ // Blocks in [indexFirstChangedBlock, firstLine) are not redrawn here. They will
+ // be redrawn after they get scrolled into drawing range.
+ continue;
}
-
- final boolean blockDisplayListIsInvalid =
- mTextRenderNodes[blockIndex].needsRecord();
- RenderNode blockDisplayList = mTextRenderNodes[blockIndex].renderNode;
- if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
- final int blockBeginLine = endOfPreviousBlock + 1;
- final int top = layout.getLineTop(blockBeginLine);
- final int bottom = layout.getLineBottom(blockEndLine);
- int left = 0;
- int right = mTextView.getWidth();
- if (mTextView.getHorizontallyScrolling()) {
- float min = Float.MAX_VALUE;
- float max = Float.MIN_VALUE;
- for (int line = blockBeginLine; line <= blockEndLine; line++) {
- min = Math.min(min, layout.getLineLeft(line));
- max = Math.max(max, layout.getLineRight(line));
- }
- left = (int) min;
- right = (int) (max + 0.5f);
- }
-
- // Rebuild display list if it is invalid
- if (blockDisplayListIsInvalid) {
- final DisplayListCanvas displayListCanvas = blockDisplayList.start(
- right - left, bottom - top);
- try {
- // drawText is always relative to TextView's origin, this translation
- // brings this range of text back to the top left corner of the viewport
- displayListCanvas.translate(-left, -top);
- layout.drawText(displayListCanvas, blockBeginLine, blockEndLine);
- mTextRenderNodes[blockIndex].isDirty = false;
- // No need to untranslate, previous context is popped after
- // drawDisplayList
- } finally {
- blockDisplayList.end(displayListCanvas);
- // Same as drawDisplayList below, handled by our TextView's parent
- blockDisplayList.setClipToBounds(false);
- }
- }
-
- // Valid disply list whose index is >= indexFirstChangedBlock
- // only needs to update its drawing location.
- blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
+ startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, layout,
+ highlight, highlightPaint, cursorOffsetVertical, blockEndLines,
+ blockIndices, i, numberOfBlocks, startIndexToFindAvailableRenderNode);
+ if (blockEndLines[i] >= lastLine) {
+ lastIndex = Math.max(indexFirstChangedBlock, i + 1);
+ break;
}
-
- ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList);
-
- endOfPreviousBlock = blockEndLine;
}
- dynamicLayout.setIndexFirstChangedBlock(numberOfBlocks);
+ dynamicLayout.setIndexFirstChangedBlock(lastIndex);
} else {
// Boring layout is used for empty and hint text
layout.drawText(canvas, firstLine, lastLine);
}
}
+ private int drawHardwareAcceleratedInner(Canvas canvas, Layout layout, Path highlight,
+ Paint highlightPaint, int cursorOffsetVertical, int[] blockEndLines,
+ int[] blockIndices, int blockInfoIndex, int numberOfBlocks,
+ int startIndexToFindAvailableRenderNode) {
+ final int blockEndLine = blockEndLines[blockInfoIndex];
+ int blockIndex = blockIndices[blockInfoIndex];
+
+ final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
+ if (blockIsInvalid) {
+ blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
+ startIndexToFindAvailableRenderNode);
+ // Note how dynamic layout's internal block indices get updated from Editor
+ blockIndices[blockInfoIndex] = blockIndex;
+ if (mTextRenderNodes[blockIndex] != null) {
+ mTextRenderNodes[blockIndex].isDirty = true;
+ }
+ startIndexToFindAvailableRenderNode = blockIndex + 1;
+ }
+
+ if (mTextRenderNodes[blockIndex] == null) {
+ mTextRenderNodes[blockIndex] = new TextRenderNode("Text " + blockIndex);
+ }
+
+ final boolean blockDisplayListIsInvalid = mTextRenderNodes[blockIndex].needsRecord();
+ RenderNode blockDisplayList = mTextRenderNodes[blockIndex].renderNode;
+ if (mTextRenderNodes[blockIndex].needsToBeShifted || blockDisplayListIsInvalid) {
+ final int blockBeginLine = blockInfoIndex == 0 ?
+ 0 : blockEndLines[blockInfoIndex - 1] + 1;
+ final int top = layout.getLineTop(blockBeginLine);
+ final int bottom = layout.getLineBottom(blockEndLine);
+ int left = 0;
+ int right = mTextView.getWidth();
+ if (mTextView.getHorizontallyScrolling()) {
+ float min = Float.MAX_VALUE;
+ float max = Float.MIN_VALUE;
+ for (int line = blockBeginLine; line <= blockEndLine; line++) {
+ min = Math.min(min, layout.getLineLeft(line));
+ max = Math.max(max, layout.getLineRight(line));
+ }
+ left = (int) min;
+ right = (int) (max + 0.5f);
+ }
+
+ // Rebuild display list if it is invalid
+ if (blockDisplayListIsInvalid) {
+ final DisplayListCanvas displayListCanvas = blockDisplayList.start(
+ right - left, bottom - top);
+ try {
+ // drawText is always relative to TextView's origin, this translation
+ // brings this range of text back to the top left corner of the viewport
+ displayListCanvas.translate(-left, -top);
+ layout.drawText(displayListCanvas, blockBeginLine, blockEndLine);
+ mTextRenderNodes[blockIndex].isDirty = false;
+ // No need to untranslate, previous context is popped after
+ // drawDisplayList
+ } finally {
+ blockDisplayList.end(displayListCanvas);
+ // Same as drawDisplayList below, handled by our TextView's parent
+ blockDisplayList.setClipToBounds(false);
+ }
+ }
+
+ // Valid display list only needs to update its drawing location.
+ blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
+ mTextRenderNodes[blockIndex].needsToBeShifted = false;
+ }
+ ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList);
+ return startIndexToFindAvailableRenderNode;
+ }
+
private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks,
int searchStartIndex) {
int length = mTextRenderNodes.length;