EditText caches only text in its internal display list.
Decorelate background and text in layout display. This allows
to only store the text in the editable TextView's display list.
Selection and cursor changes no longer need to invalidate the
display list, leading to faster rendering.
Change-Id: I3af3a98846e1bfe2d9ec6c42590e71bf3704595e
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bdfe940..516ce2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -76,7 +76,6 @@
int start, int end,
TextPaint paint) {
float need = 0;
- TextPaint workPaint = new TextPaint();
int next;
for (int i = start; i <= end; i = next) {
@@ -86,7 +85,7 @@
next = end;
// note, omits trailing paragraph char
- float w = measurePara(paint, workPaint, source, i, next);
+ float w = measurePara(paint, source, i, next);
if (w > need)
need = w;
@@ -189,106 +188,34 @@
* Draw this Layout on the specified canvas, with the highlight path drawn
* between the background and the text.
*
- * @param c the canvas
+ * @param canvas the canvas
* @param highlight the path of the highlight or cursor; can be null
* @param highlightPaint the paint for the highlight
* @param cursorOffsetVertical the amount to temporarily translate the
* canvas while rendering the highlight
*/
- public void draw(Canvas c, Path highlight, Paint highlightPaint,
- int cursorOffsetVertical) {
- int dtop, dbottom;
+ public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
+ int cursorOffsetVertical) {
+ final long lineRange = getLineRangeForDraw(canvas);
+ int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+ int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+ if (lastLine < 0) return;
- synchronized (sTempRect) {
- if (!c.getClipBounds(sTempRect)) {
- return;
- }
+ drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
+ firstLine, lastLine);
+ drawText(canvas, firstLine, lastLine);
+ }
- dtop = sTempRect.top;
- dbottom = sTempRect.bottom;
- }
-
- int top = 0;
- int bottom = getLineTop(getLineCount());
-
- if (dtop > top) {
- top = dtop;
- }
- if (dbottom < bottom) {
- bottom = dbottom;
- }
-
- int first = getLineForVertical(top);
- int last = getLineForVertical(bottom);
-
- int previousLineBottom = getLineTop(first);
- int previousLineEnd = getLineStart(first);
-
- TextPaint paint = mPaint;
- CharSequence buf = mText;
- int width = mWidth;
- boolean spannedText = mSpannedText;
-
+ /**
+ * @hide
+ */
+ public void drawText(Canvas canvas, int firstLine, int lastLine) {
+ int previousLineBottom = getLineTop(firstLine);
+ int previousLineEnd = getLineStart(firstLine);
ParagraphStyle[] spans = NO_PARA_SPANS;
int spanEnd = 0;
- int textLength = 0;
-
- // First, draw LineBackgroundSpans.
- // LineBackgroundSpans know nothing about the alignment, margins, or
- // direction of the layout or line. XXX: Should they?
- // They are evaluated at each line.
- if (spannedText) {
- Spanned sp = (Spanned) buf;
- textLength = buf.length();
- for (int i = first; i <= last; i++) {
- int start = previousLineEnd;
- int end = getLineStart(i+1);
- previousLineEnd = end;
-
- int ltop = previousLineBottom;
- int lbottom = getLineTop(i+1);
- previousLineBottom = lbottom;
- int lbaseline = lbottom - getLineDescent(i);
-
- if (start >= spanEnd) {
- // These should be infrequent, so we'll use this so that
- // we don't have to check as often.
- spanEnd = sp.nextSpanTransition(start, textLength,
- LineBackgroundSpan.class);
- // All LineBackgroundSpans on a line contribute to its
- // background.
- spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
- }
-
- for (int n = 0; n < spans.length; n++) {
- LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
-
- back.drawBackground(c, paint, 0, width,
- ltop, lbaseline, lbottom,
- buf, start, end,
- i);
- }
- }
- // reset to their original values
- spanEnd = 0;
- previousLineBottom = getLineTop(first);
- previousLineEnd = getLineStart(first);
- spans = NO_PARA_SPANS;
- }
-
- // There can be a highlight even without spans if we are drawing
- // a non-spanned transformation of a spanned editing buffer.
- if (highlight != null) {
- if (cursorOffsetVertical != 0) {
- c.translate(0, cursorOffsetVertical);
- }
-
- c.drawPath(highlight, highlightPaint);
-
- if (cursorOffsetVertical != 0) {
- c.translate(0, -cursorOffsetVertical);
- }
- }
+ TextPaint paint = mPaint;
+ CharSequence buf = mText;
Alignment paraAlign = mAlignment;
TabStops tabStops = null;
@@ -296,13 +223,11 @@
TextLine tl = TextLine.obtain();
- // Next draw the lines, one at a time.
- // the baseline is the top of the following line minus the current
- // line's descent.
- for (int i = first; i <= last; i++) {
+ // Draw the lines, one at a time.
+ // The baseline is the top of the following line minus the current line's descent.
+ for (int i = firstLine; i <= lastLine; i++) {
int start = previousLineEnd;
-
- previousLineEnd = getLineStart(i+1);
+ previousLineEnd = getLineStart(i + 1);
int end = getLineVisibleEnd(i, start, previousLineEnd);
int ltop = previousLineBottom;
@@ -314,10 +239,10 @@
int left = 0;
int right = mWidth;
- if (spannedText) {
+ if (mSpannedText) {
Spanned sp = (Spanned) buf;
- boolean isFirstParaLine = (start == 0 ||
- buf.charAt(start - 1) == '\n');
+ int textLength = buf.length();
+ boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
// New batch of paragraph styles, collect into spans array.
// Compute the alignment, last alignment style wins.
@@ -329,13 +254,13 @@
// just collect the ones present at the start of the paragraph.
// If spanEnd is before the end of the paragraph, that's not
// our problem.
- if (start >= spanEnd && (i == first || isFirstParaLine)) {
+ if (start >= spanEnd && (i == firstLine || isFirstParaLine)) {
spanEnd = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
paraAlign = mAlignment;
- for (int n = spans.length-1; n >= 0; n--) {
+ for (int n = spans.length - 1; n >= 0; n--) {
if (spans[n] instanceof AlignmentSpan) {
paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
break;
@@ -359,12 +284,12 @@
}
if (dir == DIR_RIGHT_TO_LEFT) {
- margin.drawLeadingMargin(c, paint, right, dir, ltop,
+ margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
right -= margin.getLeadingMargin(useFirstLineMargin);
} else {
- margin.drawLeadingMargin(c, paint, left, dir, ltop,
+ margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
left += margin.getLeadingMargin(useFirstLineMargin);
@@ -416,13 +341,12 @@
}
Directions directions = getLineDirections(i);
- if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
- !spannedText && !hasTabOrEmoji) {
+ if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
// XXX: assumes there's nothing additional to be done
- c.drawText(buf, start, end, x, lbaseline, paint);
+ canvas.drawText(buf, start, end, x, lbaseline, paint);
} else {
tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
- tl.draw(c, x, ltop, lbaseline, lbottom);
+ tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
}
@@ -430,6 +354,85 @@
}
/**
+ * @hide
+ */
+ public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
+ int cursorOffsetVertical, int firstLine, int lastLine) {
+ // First, draw LineBackgroundSpans.
+ // LineBackgroundSpans know nothing about the alignment, margins, or
+ // direction of the layout or line. XXX: Should they?
+ // They are evaluated at each line.
+ if (mSpannedText) {
+ int previousLineBottom = getLineTop(firstLine);
+ int previousLineEnd = getLineStart(firstLine);
+ ParagraphStyle[] spans = NO_PARA_SPANS;
+ TextPaint paint = mPaint;
+ CharSequence buf = mText;
+ int spanEnd = 0;
+ final int width = mWidth;
+ Spanned sp = (Spanned) buf;
+ int textLength = buf.length();
+ for (int i = firstLine; i <= lastLine; i++) {
+ int start = previousLineEnd;
+ int end = getLineStart(i + 1);
+ previousLineEnd = end;
+
+ int ltop = previousLineBottom;
+ int lbottom = getLineTop(i + 1);
+ previousLineBottom = lbottom;
+ int lbaseline = lbottom - getLineDescent(i);
+
+ if (start >= spanEnd) {
+ // These should be infrequent, so we'll use this so that
+ // we don't have to check as often.
+ spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class);
+ // All LineBackgroundSpans on a line contribute to its background.
+ spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
+ }
+
+ for (int n = 0; n < spans.length; n++) {
+ LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
+ back.drawBackground(canvas, paint, 0, width,
+ ltop, lbaseline, lbottom,
+ buf, start, end, i);
+ }
+ }
+ }
+
+ // There can be a highlight even without spans if we are drawing
+ // a non-spanned transformation of a spanned editing buffer.
+ if (highlight != null) {
+ if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
+ canvas.drawPath(highlight, highlightPaint);
+ if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
+ }
+ }
+
+ /**
+ * @param canvas
+ * @return The range of lines that need to be drawn, possibly empty.
+ * @hide
+ */
+ public long getLineRangeForDraw(Canvas canvas) {
+ int dtop, dbottom;
+
+ synchronized (sTempRect) {
+ if (!canvas.getClipBounds(sTempRect)) {
+ // Negative range end used as a special flag
+ return TextUtils.packRangeInLong(0, -1);
+ }
+
+ dtop = sTempRect.top;
+ dbottom = sTempRect.bottom;
+ }
+
+ final int top = Math.max(dtop, 0);
+ final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
+
+ return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
+ }
+
+ /**
* Return the start position of the line, given the left and right bounds
* of the margins.
*
@@ -460,7 +463,8 @@
int start = getLineStart(line);
int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
TabStopSpan.class);
- TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class);
+ TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
+ TabStopSpan.class);
if (tabSpans.length > 0) {
tabStops = new TabStops(TAB_INCREMENT, tabSpans);
}
@@ -1481,8 +1485,7 @@
}
/* package */
- static float measurePara(TextPaint paint, TextPaint workPaint,
- CharSequence text, int start, int end) {
+ static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
@@ -1659,7 +1662,7 @@
*/
/* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
if (start == end && start > 0) {
- return (T[]) ArrayUtils.emptyArray(type);
+ return ArrayUtils.emptyArray(type);
}
return text.getSpans(start, end, type);
@@ -1777,8 +1780,7 @@
}
- /* package */ static class SpannedEllipsizer
- extends Ellipsizer implements Spanned {
+ /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
private Spanned mSpanned;
public SpannedEllipsizer(CharSequence display) {
@@ -1802,6 +1804,7 @@
return mSpanned.getSpanFlags(tag);
}
+ @SuppressWarnings("rawtypes")
public int nextSpanTransition(int start, int limit, Class type) {
return mSpanned.nextSpanTransition(start, limit, type);
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 43dfc81..afae5bb2 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1664,6 +1664,36 @@
}
}
+ /**
+ * Pack 2 int values into a long, useful as a return value for a range
+ * @see #unpackRangeStartFromLong(long)
+ * @see #unpackRangeEndFromLong(long)
+ * @hide
+ */
+ public static long packRangeInLong(int start, int end) {
+ return (((long) start) << 32) | end;
+ }
+
+ /**
+ * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)}
+ * @see #unpackRangeEndFromLong(long)
+ * @see #packRangeInLong(int, int)
+ * @hide
+ */
+ public static int unpackRangeStartFromLong(long range) {
+ return (int) (range >>> 32);
+ }
+
+ /**
+ * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)}
+ * @see #unpackRangeStartFromLong(long)
+ * @see #packRangeInLong(int, int)
+ * @hide
+ */
+ public static int unpackRangeEndFromLong(long range) {
+ return (int) (range & 0x00000000FFFFFFFFL);
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 56a0d1e..cbacd7a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6946,7 +6946,6 @@
*/
protected void onSelectionChanged(int selStart, int selEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
- if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
}
/**
@@ -7772,18 +7771,6 @@
hasPrimaryClip());
}
- private static long packRangeInLong(int start, int end) {
- return (((long) start) << 32) | end;
- }
-
- private static int extractRangeStartFromLong(long range) {
- return (int) (range >>> 32);
- }
-
- private static int extractRangeEndFromLong(long range) {
- return (int) (range & 0x00000000FFFFFFFFL);
- }
-
private boolean selectAll() {
final int length = mText.length();
Selection.setSelection((Spannable) mText, 0, length);
@@ -7822,8 +7809,8 @@
}
long lastTouchOffsets = getLastTouchOffsets();
- final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
- final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
+ final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets);
+ final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
// Safety check in case standard touch event handling has been bypassed
if (minOffset < 0 || minOffset >= mText.length()) return false;
@@ -7848,8 +7835,8 @@
selectionStart == selectionEnd) {
// Possible when the word iterator does not properly handle the text's language
long range = getCharRange(minOffset);
- selectionStart = extractRangeStartFromLong(range);
- selectionEnd = extractRangeEndFromLong(range);
+ selectionStart = TextUtils.unpackRangeStartFromLong(range);
+ selectionEnd = TextUtils.unpackRangeEndFromLong(range);
}
}
@@ -7897,30 +7884,30 @@
final char currentChar = mText.charAt(offset);
final char nextChar = mText.charAt(offset + 1);
if (Character.isSurrogatePair(currentChar, nextChar)) {
- return packRangeInLong(offset, offset + 2);
+ return TextUtils.packRangeInLong(offset, offset + 2);
}
}
if (offset < textLength) {
- return packRangeInLong(offset, offset + 1);
+ return TextUtils.packRangeInLong(offset, offset + 1);
}
if (offset - 2 >= 0) {
final char previousChar = mText.charAt(offset - 1);
final char previousPreviousChar = mText.charAt(offset - 2);
if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
- return packRangeInLong(offset - 2, offset);
+ return TextUtils.packRangeInLong(offset - 2, offset);
}
}
if (offset - 1 >= 0) {
- return packRangeInLong(offset - 1, offset);
+ return TextUtils.packRangeInLong(offset - 1, offset);
}
- return packRangeInLong(offset, offset);
+ return TextUtils.packRangeInLong(offset, offset);
}
private long getLastTouchOffsets() {
SelectionModifierCursorController selectionController = getSelectionController();
final int minOffset = selectionController.getMinTouchOffset();
final int maxOffset = selectionController.getMaxTouchOffset();
- return packRangeInLong(minOffset, maxOffset);
+ return TextUtils.packRangeInLong(minOffset, maxOffset);
}
@Override
@@ -8111,7 +8098,7 @@
}
}
- return packRangeInLong(min, max);
+ return TextUtils.packRangeInLong(min, max);
}
private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
@@ -8470,8 +8457,8 @@
if (paste != null) {
if (!didFirst) {
long minMax = prepareSpacesAroundPaste(min, max, paste);
- min = extractRangeStartFromLong(minMax);
- max = extractRangeEndFromLong(minMax);
+ min = TextUtils.unpackRangeStartFromLong(minMax);
+ max = TextUtils.unpackRangeEndFromLong(minMax);
Selection.setSelection((Spannable) mText, max);
((Editable) mText).replace(min, max, paste);
didFirst = true;
@@ -8630,8 +8617,8 @@
final int originalLength = mText.length();
long minMax = prepareSpacesAroundPaste(offset, offset, content);
- int min = extractRangeStartFromLong(minMax);
- int max = extractRangeEndFromLong(minMax);
+ int min = TextUtils.unpackRangeStartFromLong(minMax);
+ int max = TextUtils.unpackRangeEndFromLong(minMax);
Selection.setSelection((Spannable) mText, max);
replaceText_internal(min, max, content);
@@ -11667,33 +11654,7 @@
}
if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
- final int width = mRight - mLeft;
- final int height = mBottom - mTop;
-
- if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
- !mTextDisplayListIsValid) {
- if (mTextDisplayList == null) {
- mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
- }
-
- 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.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
- hardwareCanvas.translate(mScrollX, mScrollY);
- } finally {
- hardwareCanvas.onPostDraw();
- mTextDisplayList.end();
- mTextDisplayListIsValid = true;
- }
- }
- canvas.translate(mScrollX, mScrollY);
- ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
- DisplayList.FLAG_CLIP_CHILDREN);
- canvas.translate(-mScrollX, -mScrollY);
+ drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
@@ -11704,6 +11665,46 @@
}
}
+ private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
+ int cursorOffsetVertical) {
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ final long lineRange = layout.getLineRangeForDraw(canvas);
+ int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+ int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+ if (lastLine < 0) return;
+
+ layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical,
+ firstLine, lastLine);
+
+ if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
+ !mTextDisplayListIsValid) {
+ if (mTextDisplayList == null) {
+ mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
+ }
+
+ 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();
+ mTextDisplayListIsValid = true;
+ }
+ }
+ canvas.translate(mScrollX, mScrollY);
+ ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ canvas.translate(-mScrollX, -mScrollY);
+ }
+
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);