diff options
author | Gilles Debunne <debunne@google.com> | 2012-03-01 16:20:35 -0800 |
---|---|---|
committer | Gilles Debunne <debunne@google.com> | 2012-03-05 14:22:20 -0800 |
commit | 6c488de023a4797069673dc619c1a4096079ea9e (patch) | |
tree | 5559ad0aabdc5b567776e015e18f8b8d4b243487 /core/java | |
parent | 5a2b6077001d948f5d7667829c95b06228556609 (diff) | |
download | frameworks_base-6c488de023a4797069673dc619c1a4096079ea9e.zip frameworks_base-6c488de023a4797069673dc619c1a4096079ea9e.tar.gz frameworks_base-6c488de023a4797069673dc619c1a4096079ea9e.tar.bz2 |
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
Diffstat (limited to 'core/java')
-rw-r--r-- | core/java/android/text/Layout.java | 237 | ||||
-rw-r--r-- | core/java/android/text/TextUtils.java | 30 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 111 |
3 files changed, 206 insertions, 172 deletions
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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { * 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; - - synchronized (sTempRect) { - if (!c.getClipBounds(sTempRect)) { - return; - } - - 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; + 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; + + drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, + firstLine, lastLine); + drawText(canvas, firstLine, lastLine); + } + /** + * @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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { // 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 @@ public abstract class Layout { } 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 @@ public abstract class Layout { } 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 @@ public abstract class Layout { } /** + * @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 @@ public abstract class Layout { 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 @@ public abstract class Layout { } /* 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 @@ public abstract class Layout { */ /* 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 @@ public abstract class Layout { } - /* 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 @@ public abstract class Layout { 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 @@ public class TextUtils { } } + /** + * 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); - if (mEditor != null) getEditor().mTextDisplayListIsValid = false; } /** @@ -7772,18 +7771,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - return packRangeInLong(min, max); + return TextUtils.packRangeInLong(min, max); } private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { @@ -8470,8 +8457,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + 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); |