Merge "EditText caches only text in its internal display list."
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);