summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gilles Debunne <debunne@google.com> 2012-03-15 16:35:56 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2012-03-15 16:35:56 -0700
commit590924e10a39347e2375a2e4f96be95883637be4 (patch)
tree51a6c38030f80ce324a351806bbf46b02af576ad
parent19e27c051d60974c3991c1ce252387ec6e568736 (diff)
parent33b7de85b6918b7714641f12f1ba2ff03a344740 (diff)
Merge "Multiple display lists for editable text"
-rw-r--r--core/java/android/text/DynamicLayout.java145
-rw-r--r--core/java/android/widget/TextView.java118
2 files changed, 232 insertions, 31 deletions
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 026af34445f6..a1beb0df296b 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -20,6 +20,8 @@ import android.graphics.Paint;
import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
+import com.android.internal.util.ArrayUtils;
+
import java.lang.ref.WeakReference;
/**
@@ -30,8 +32,7 @@ import java.lang.ref.WeakReference;
* {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
* Canvas.drawText()} directly.</p>
*/
-public class DynamicLayout
-extends Layout
+public class DynamicLayout extends Layout
{
private static final int PRIORITY = 128;
@@ -116,6 +117,10 @@ extends Layout
mObjects = new PackedObjectVector<Directions>(1);
+ mBlockEnds = new int[] { 0 };
+ mBlockIndices = new int[] { INVALID_BLOCK_INDEX };
+ mNumberOfBlocks = 1;
+
mIncludePad = includepad;
/*
@@ -295,9 +300,9 @@ extends Layout
n--;
// remove affected lines from old layout
-
mInts.deleteAt(startline, endline - startline);
mObjects.deleteAt(startline, endline - startline);
+ updateBlocks(startline, endline - 1, n);
// adjust offsets in layout for new height and offsets
@@ -363,6 +368,124 @@ extends Layout
}
}
+ /**
+ * This method is called every time the layout is reflowed after an edition.
+ * It updates the internal block data structure. The text is split in blocks
+ * of contiguous lines, with at least one block for the entire text.
+ * When a range of lines is edited, new blocks (from 0 to 3 depending on the
+ * overlap structure) will replace the set of overlapping blocks.
+ * Blocks are listed in order and are represented by their ending line number.
+ * An index is associated to each block (which will be used by display lists),
+ * this class simply invalidates the index of blocks overlapping a modification.
+ *
+ * @param startLine the first line of the range of modified lines
+ * @param endLine the last line of the range, possibly equal to startLine, lower
+ * than getLineCount()
+ * @param newLineCount the number of lines that will replace the range, possibly 0
+ */
+ private void updateBlocks(int startLine, int endLine, int newLineCount) {
+ int firstBlock = -1;
+ int lastBlock = -1;
+ for (int i = 0; i < mNumberOfBlocks; i++) {
+ if (mBlockEnds[i] >= startLine) {
+ firstBlock = i;
+ break;
+ }
+ }
+ for (int i = firstBlock; i < mNumberOfBlocks; i++) {
+ if (mBlockEnds[i] >= endLine) {
+ lastBlock = i;
+ break;
+ }
+ }
+ final int lastBlockEndLine = mBlockEnds[lastBlock];
+
+ boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
+ mBlockEnds[firstBlock - 1] + 1);
+ boolean createBlock = newLineCount > 0;
+ boolean createBlockAfter = endLine < mBlockEnds[lastBlock];
+
+ int numAddedBlocks = 0;
+ if (createBlockBefore) numAddedBlocks++;
+ if (createBlock) numAddedBlocks++;
+ if (createBlockAfter) numAddedBlocks++;
+
+ final int numRemovedBlocks = lastBlock - firstBlock + 1;
+ final int newNumberOfBlocks = mNumberOfBlocks + numAddedBlocks - numRemovedBlocks;
+
+ if (newNumberOfBlocks == 0) {
+ // Even when text is empty, there is actually one line and hence one block
+ mBlockEnds[0] = 0;
+ mBlockIndices[0] = INVALID_BLOCK_INDEX;
+ mNumberOfBlocks = 1;
+ return;
+ }
+
+ if (newNumberOfBlocks > mBlockEnds.length) {
+ final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
+ int[] blockEnds = new int[newSize];
+ int[] blockIndices = new int[newSize];
+ System.arraycopy(mBlockEnds, 0, blockEnds, 0, firstBlock);
+ System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
+ System.arraycopy(mBlockEnds, lastBlock + 1,
+ blockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ System.arraycopy(mBlockIndices, lastBlock + 1,
+ blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ mBlockEnds = blockEnds;
+ mBlockIndices = blockIndices;
+ } else {
+ System.arraycopy(mBlockEnds, lastBlock + 1,
+ mBlockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ System.arraycopy(mBlockIndices, lastBlock + 1,
+ mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ }
+
+ mNumberOfBlocks = newNumberOfBlocks;
+ final int deltaLines = newLineCount - (endLine - startLine + 1);
+ for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
+ mBlockEnds[i] += deltaLines;
+ }
+
+ int blockIndex = firstBlock;
+ if (createBlockBefore) {
+ mBlockEnds[blockIndex] = startLine - 1;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ blockIndex++;
+ }
+
+ if (createBlock) {
+ mBlockEnds[blockIndex] = startLine + newLineCount - 1;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ blockIndex++;
+ }
+
+ if (createBlockAfter) {
+ mBlockEnds[blockIndex] = lastBlockEndLine + deltaLines;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getBlockEnds() {
+ return mBlockEnds;
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getBlockIndices() {
+ return mBlockIndices;
+ }
+
+ /**
+ * @hide
+ */
+ public int getNumberOfBlocks() {
+ return mNumberOfBlocks;
+ }
+
@Override
public int getLineCount() {
return mInts.size() - 1;
@@ -428,6 +551,7 @@ extends Layout
}
public void beforeTextChanged(CharSequence s, int where, int before, int after) {
+ // Intentionally empty
}
public void onTextChanged(CharSequence s, int where, int before, int after) {
@@ -435,6 +559,7 @@ extends Layout
}
public void afterTextChanged(Editable s) {
+ // Intentionally empty
}
public void onSpanAdded(Spannable s, Object o, int start, int end) {
@@ -486,6 +611,20 @@ extends Layout
private PackedIntVector mInts;
private PackedObjectVector<Directions> mObjects;
+ /*
+ * Value used in mBlockIndices when a block has been created or recycled and indicating that its
+ * display list needs to be re-created.
+ * @hide
+ */
+ public static final int INVALID_BLOCK_INDEX = -1;
+ // Stores the line numbers of the last line of each block
+ private int[] mBlockEnds;
+ // The indices of this block's display list in TextView's internal display list array or
+ // INVALID_BLOCK_INDEX if this block has been invalidated during an edition
+ private int[] mBlockIndices;
+ // Number of items actually currently being used in the above 2 arrays
+ private int mNumberOfBlocks;
+
private int mTopPadding, mBottomPadding;
private static StaticLayout sStaticLayout = new StaticLayout(null);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e535170e56f9..9941c951a953 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -139,6 +139,7 @@ import android.view.textservice.TextServicesManager;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.widget.EditableInputConnection;
@@ -1214,6 +1215,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (imm != null) imm.restartInput(this);
}
+ // Will change text color
if (mEditor != null) getEditor().invalidateTextDisplayList();
prepareCursorControllers();
@@ -2328,7 +2330,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
- if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
}
@@ -2349,6 +2350,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShadowDx = dx;
mShadowDy = dy;
+ // Will change text clip region
if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
@@ -2841,6 +2843,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (inval) {
+ // Text needs to be redrawn with the new color
if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
@@ -3332,7 +3335,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
invalidate();
}
- // Invalidate display list if hint will be used
+ // Invalidate display list if hint is currently used
if (mEditor != null && mText.length() == 0 && mHint != null) {
getEditor().invalidateTextDisplayList();
}
@@ -8274,6 +8277,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (getEditor().mPositionListener != null) {
getEditor().mPositionListener.onScrollChanged();
}
+ // Internal scroll affects the clip boundaries
getEditor().invalidateTextDisplayList();
}
}
@@ -11299,7 +11303,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
InputContentType mInputContentType;
InputMethodState mInputMethodState;
- DisplayList mTextDisplayList;
+ DisplayList[] mTextDisplayLists;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -11545,7 +11549,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void sendOnTextChanged(int start, int after) {
updateSpellCheckSpans(start, start + after, false);
- invalidateTextDisplayList();
// Hide the controllers as soon as text is modified (typing, procedural...)
// We do not hide the span controllers, since they can be added when a new text is
@@ -11702,36 +11705,91 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical,
firstLine, lastLine);
- if (mTextDisplayList == null || !mTextDisplayList.isValid()) {
- boolean displayListCreated = false;
- if (mTextDisplayList == null) {
- mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
- displayListCreated = true;
- }
+ if (mTextDisplayLists == null) {
+ mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
+ }
+ if (! (layout instanceof DynamicLayout)) {
+ Log.e(LOG_TAG, "Editable TextView is not using a DynamicLayout");
+ return;
+ }
- final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
- try {
- hardwareCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- hardwareCanvas.onPreDraw(null);
- hardwareCanvas.translate(-mScrollX, -mScrollY);
- layout.drawText(hardwareCanvas, firstLine, lastLine);
- //layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
- hardwareCanvas.translate(mScrollX, mScrollY);
- } finally {
- hardwareCanvas.onPostDraw();
- mTextDisplayList.end();
- if (displayListCreated && USE_DISPLAY_LIST_PROPERTIES) {
- mTextDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ DynamicLayout dynamicLayout = (DynamicLayout) layout;
+ int[] blockEnds = dynamicLayout.getBlockEnds();
+ int[] blockIndices = dynamicLayout.getBlockIndices();
+ final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
+
+ canvas.translate(mScrollX, mScrollY);
+ int endOfPreviousBlock = -1;
+ int searchStartIndex = 0;
+ for (int i = 0; i < numberOfBlocks; i++) {
+ int blockEnd = blockEnds[i];
+ int blockIndex = blockIndices[i];
+
+ final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
+ if (blockIsInvalid) {
+ blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
+ searchStartIndex);
+ // Dynamic layout internal block indices structure is updated from Editor
+ blockIndices[i] = blockIndex;
+ searchStartIndex = blockIndex + 1;
+ }
+
+ DisplayList blockDisplayList = mTextDisplayLists[blockIndex];
+ if (blockDisplayList == null) {
+ blockDisplayList = mTextDisplayLists[blockIndex] =
+ getHardwareRenderer().createDisplayList("Text " + blockIndex);
+ } else {
+ if (blockIsInvalid) blockDisplayList.invalidate();
+ }
+
+ if (!blockDisplayList.isValid()) {
+ final HardwareCanvas hardwareCanvas = blockDisplayList.start();
+ try {
+ hardwareCanvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ hardwareCanvas.onPreDraw(null);
+ hardwareCanvas.translate(-mScrollX, -mScrollY);
+ layout.drawText(hardwareCanvas, endOfPreviousBlock + 1, blockEnd);
+ hardwareCanvas.translate(mScrollX, mScrollY);
+ } finally {
+ hardwareCanvas.onPostDraw();
+ blockDisplayList.end();
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+ blockDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ }
}
}
+
+ ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, width, height, null,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ endOfPreviousBlock = blockEnd;
}
- canvas.translate(mScrollX, mScrollY);
- ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
- DisplayList.FLAG_CLIP_CHILDREN);
canvas.translate(-mScrollX, -mScrollY);
}
+ private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks,
+ int searchStartIndex) {
+ int length = mTextDisplayLists.length;
+ for (int i = searchStartIndex; i < length; i++) {
+ boolean blockIndexFound = false;
+ for (int j = 0; j < numberOfBlocks; j++) {
+ if (blockIndices[j] == i) {
+ blockIndexFound = true;
+ break;
+ }
+ }
+ if (blockIndexFound) continue;
+ return i;
+ }
+
+ // No available index found, the pool has to grow
+ int newSize = ArrayUtils.idealIntArraySize(length + 1);
+ DisplayList[] displayLists = new DisplayList[newSize];
+ System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length);
+ mTextDisplayLists = displayLists;
+ return length;
+ }
+
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
@@ -11742,7 +11800,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void invalidateTextDisplayList() {
- if (mTextDisplayList != null) mTextDisplayList.invalidate();
+ if (mTextDisplayLists != null) {
+ for (int i = 0; i < mTextDisplayLists.length; i++) {
+ if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate();
+ }
+ }
}
private void updateCursorsPositions() {