diff options
| author | 2012-03-15 16:35:56 -0700 | |
|---|---|---|
| committer | 2012-03-15 16:35:56 -0700 | |
| commit | 590924e10a39347e2375a2e4f96be95883637be4 (patch) | |
| tree | 51a6c38030f80ce324a351806bbf46b02af576ad | |
| parent | 19e27c051d60974c3991c1ce252387ec6e568736 (diff) | |
| parent | 33b7de85b6918b7714641f12f1ba2ff03a344740 (diff) | |
Merge "Multiple display lists for editable text"
| -rw-r--r-- | core/java/android/text/DynamicLayout.java | 145 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 118 |
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() { |