Introduce PremeasuredText
By measuring the character widths beforehand, we can save at least 40% of the StaticLayout construction time which typically happens on UI thread. Also verified this doesn't cause performance regression for not premeasured text. Raw performance score (Not premeasured -> premeasured, median, N=100) No Style, Greedy, Hyphenation OFF: 7,812,975 -> 503,245 (-93.6%) No Style, Balanced, Hyphenation OFF: 7,843,254 -> 396,892 (-95.0%) No Style, Greedy, Hyphenation ON : 19,134,214 -> 11,658,928 (-39.1%) No Style, Balanced, Hyphenation ON : 19,348,062 -> 11,634,942 (-39.9%) Styled, Greedy, Hyphenation OFF: 14,353,673 -> 572,840 (-96.0%) Raw performance score (w/o patch -> w/ patch, median, N=100): No Style, Greedy, Hyphenation OFF: 7,732,894 -> 7,812,975 (+1.04%) No Style, Balanced, Hyphenation OFF: 7,884,510 -> 7,843,254 (-0.52%) No Style, Greedy, Hyphenation ON : 18,986,958 -> 19,134,214 (+0.78%) No Style, Balanced, Hyphenation ON : 19,232,791 -> 19,348,062 (+0.60%) Styled, Greedy, Hyphenation OFF: 14,319,690 -> 14,353,673 (+0.24%) Bug: 67504091 Test: bit CtsTextTestCases:* Test: bit CtsGraphicsTestCases:* Test: bit CtsWidgetTestCases:* Test: FrameworksCoreTests:android.text.MeasuredTextTest Change-Id: I0b46f04b42cc012606a9c722eca0d51147a0dcc7
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.text;
|
||||
|
||||
import static android.text.TextDirectionHeuristics.LTR;
|
||||
|
||||
import android.perftests.utils.BenchmarkState;
|
||||
import android.perftests.utils.PerfStatusReporter;
|
||||
|
||||
@@ -182,4 +184,84 @@ public class StaticLayoutPerfTest {
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final PremeasuredText text = PremeasuredText.build(
|
||||
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final PremeasuredText text = PremeasuredText.build(
|
||||
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final PremeasuredText text = PremeasuredText.build(
|
||||
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final PremeasuredText text = PremeasuredText.build(
|
||||
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
state.pauseTiming();
|
||||
final PremeasuredText text = PremeasuredText.build(
|
||||
generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
|
||||
state.resumeTiming();
|
||||
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
|
||||
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42276,6 +42276,27 @@ package android.text {
|
||||
method public abstract int getSpanTypeId();
|
||||
}
|
||||
|
||||
public class PremeasuredText implements android.text.Spanned {
|
||||
method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
|
||||
method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
|
||||
method public char charAt(int);
|
||||
method public int getEnd();
|
||||
method public android.text.TextPaint getPaint();
|
||||
method public int getParagraphCount();
|
||||
method public int getParagraphEnd(int);
|
||||
method public int getParagraphStart(int);
|
||||
method public int getSpanEnd(java.lang.Object);
|
||||
method public int getSpanFlags(java.lang.Object);
|
||||
method public int getSpanStart(java.lang.Object);
|
||||
method public <T> T[] getSpans(int, int, java.lang.Class<T>);
|
||||
method public int getStart();
|
||||
method public java.lang.CharSequence getText();
|
||||
method public android.text.TextDirectionHeuristic getTextDir();
|
||||
method public int length();
|
||||
method public int nextSpanTransition(int, int, java.lang.Class);
|
||||
method public java.lang.CharSequence subSequence(int, int);
|
||||
}
|
||||
|
||||
public class Selection {
|
||||
method public static boolean extendDown(android.text.Spannable, android.text.Layout);
|
||||
method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
|
||||
|
||||
272
core/java/android/text/PremeasuredText.java
Normal file
272
core/java/android/text/PremeasuredText.java
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.IntArray;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A text which has already been measured.
|
||||
*
|
||||
* TODO: Rename to better name? e.g. MeasuredText, FrozenText etc.
|
||||
*/
|
||||
public class PremeasuredText implements Spanned {
|
||||
private static final char LINE_FEED = '\n';
|
||||
|
||||
// The original text.
|
||||
private final @NonNull CharSequence mText;
|
||||
|
||||
// The inclusive start offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mStart;
|
||||
|
||||
// The exclusive end offset of the measuring target.
|
||||
private final @IntRange(from = 0) int mEnd;
|
||||
|
||||
// The TextPaint used for measurement.
|
||||
private final @NonNull TextPaint mPaint;
|
||||
|
||||
// The requested text direction.
|
||||
private final @NonNull TextDirectionHeuristic mTextDir;
|
||||
|
||||
// The measured paragraph texts.
|
||||
private final @NonNull MeasuredText[] mMeasuredTexts;
|
||||
|
||||
// The sorted paragraph end offsets.
|
||||
private final @NonNull int[] mParagraphBreakPoints;
|
||||
|
||||
/**
|
||||
* Build PremeasuredText from the text.
|
||||
*
|
||||
* @param text The text to be measured.
|
||||
* @param paint The paint to be used for drawing.
|
||||
* @param textDir The text direction.
|
||||
* @return The measured text.
|
||||
*/
|
||||
public static @NonNull PremeasuredText build(@NonNull CharSequence text,
|
||||
@NonNull TextPaint paint,
|
||||
@NonNull TextDirectionHeuristic textDir) {
|
||||
return PremeasuredText.build(text, paint, textDir, 0, text.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build PremeasuredText from the specific range of the text..
|
||||
*
|
||||
* @param text The text to be measured.
|
||||
* @param paint The paint to be used for drawing.
|
||||
* @param textDir The text direction.
|
||||
* @param start The inclusive start offset of the text.
|
||||
* @param end The exclusive start offset of the text.
|
||||
* @return The measured text.
|
||||
*/
|
||||
public static @NonNull PremeasuredText build(@NonNull CharSequence text,
|
||||
@NonNull TextPaint paint,
|
||||
@NonNull TextDirectionHeuristic textDir,
|
||||
@IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end) {
|
||||
Preconditions.checkNotNull(text);
|
||||
Preconditions.checkNotNull(paint);
|
||||
Preconditions.checkNotNull(textDir);
|
||||
Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
|
||||
Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
|
||||
|
||||
final IntArray paragraphEnds = new IntArray();
|
||||
final ArrayList<MeasuredText> measuredTexts = new ArrayList<>();
|
||||
|
||||
int paraEnd = 0;
|
||||
for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
|
||||
paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
|
||||
if (paraEnd < 0) {
|
||||
// No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
|
||||
paraEnd = end;
|
||||
} else {
|
||||
paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
|
||||
}
|
||||
|
||||
paragraphEnds.add(paraEnd);
|
||||
measuredTexts.add(MeasuredText.buildForStaticLayout(
|
||||
paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
|
||||
}
|
||||
|
||||
return new PremeasuredText(text, start, end, paint, textDir,
|
||||
measuredTexts.toArray(new MeasuredText[measuredTexts.size()]),
|
||||
paragraphEnds.toArray());
|
||||
}
|
||||
|
||||
// Use PremeasuredText.build instead.
|
||||
private PremeasuredText(@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end,
|
||||
@NonNull TextPaint paint,
|
||||
@NonNull TextDirectionHeuristic textDir,
|
||||
@NonNull MeasuredText[] measuredTexts,
|
||||
@NonNull int[] paragraphBreakPoints) {
|
||||
mText = text;
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
mPaint = paint;
|
||||
mMeasuredTexts = measuredTexts;
|
||||
mParagraphBreakPoints = paragraphBreakPoints;
|
||||
mTextDir = textDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying text.
|
||||
*/
|
||||
public @NonNull CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inclusive start offset of measured region.
|
||||
*/
|
||||
public @IntRange(from = 0) int getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exclusive end offset of measured region.
|
||||
*/
|
||||
public @IntRange(from = 0) int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text direction associated with char sequence.
|
||||
*/
|
||||
public @NonNull TextDirectionHeuristic getTextDir() {
|
||||
return mTextDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paint used to measure this text.
|
||||
*/
|
||||
public @NonNull TextPaint getPaint() {
|
||||
return mPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the paragraph of this text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphCount() {
|
||||
return mParagraphBreakPoints.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph start offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paragraph end offset of the text.
|
||||
*/
|
||||
public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
|
||||
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
|
||||
return mParagraphBreakPoints[paraIndex];
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) {
|
||||
return mMeasuredTexts[paraIndex];
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Spanned overrides
|
||||
//
|
||||
// Just proxy for underlying mText if appropriate.
|
||||
|
||||
@Override
|
||||
public <T> T[] getSpans(int start, int end, Class<T> type) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpans(start, end, type);
|
||||
} else {
|
||||
return ArrayUtils.emptyArray(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanStart(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanStart(tag);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanEnd(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanEnd(tag);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanFlags(Object tag) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).getSpanFlags(tag);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextSpanTransition(int start, int limit, Class type) {
|
||||
if (mText instanceof Spanned) {
|
||||
return ((Spanned) mText).nextSpanTransition(start, limit, type);
|
||||
} else {
|
||||
return mText.length();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CharSequence overrides.
|
||||
//
|
||||
// Just proxy for underlying mText.
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return mText.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
// TODO: Should this be index + mStart ?
|
||||
return mText.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
// TODO: return PremeasuredText.
|
||||
// TODO: Should this be index + mStart, end + mStart ?
|
||||
return mText.subSequence(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mText.toString();
|
||||
}
|
||||
}
|
||||
@@ -610,8 +610,8 @@ public class StaticLayout extends Layout {
|
||||
|
||||
/* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
|
||||
final CharSequence source = b.mText;
|
||||
int bufStart = b.mStart;
|
||||
int bufEnd = b.mEnd;
|
||||
final int bufStart = b.mStart;
|
||||
final int bufEnd = b.mEnd;
|
||||
TextPaint paint = b.mPaint;
|
||||
int outerWidth = b.mWidth;
|
||||
TextDirectionHeuristic textDir = b.mTextDir;
|
||||
@@ -634,10 +634,6 @@ public class StaticLayout extends Layout {
|
||||
Paint.FontMetricsInt fm = b.mFontMetricsInt;
|
||||
int[] chooseHtv = null;
|
||||
|
||||
Spanned spanned = null;
|
||||
if (source instanceof Spanned)
|
||||
spanned = (Spanned) source;
|
||||
|
||||
final int[] indents;
|
||||
if (mLeftIndents != null || mRightIndents != null) {
|
||||
final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
|
||||
@@ -660,16 +656,34 @@ public class StaticLayout extends Layout {
|
||||
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
|
||||
indents, mLeftPaddings, mRightPaddings);
|
||||
|
||||
MeasuredText measured = null;
|
||||
PremeasuredText premeasured = null;
|
||||
final Spanned spanned;
|
||||
if (source instanceof PremeasuredText) {
|
||||
premeasured = (PremeasuredText) source;
|
||||
|
||||
final CharSequence original = premeasured.getText();
|
||||
spanned = (original instanceof Spanned) ? (Spanned) original : null;
|
||||
|
||||
if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
|
||||
// The buffer position has changed. Re-measure here.
|
||||
premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
|
||||
} else {
|
||||
// We can use premeasured information.
|
||||
|
||||
// Overwrite with the one when premeasured.
|
||||
// TODO: Give an option for developer not to overwrite and measure again here?
|
||||
textDir = premeasured.getTextDir();
|
||||
paint = premeasured.getPaint();
|
||||
}
|
||||
} else {
|
||||
premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
|
||||
spanned = (source instanceof Spanned) ? (Spanned) source : null;
|
||||
}
|
||||
|
||||
try {
|
||||
int paraEnd;
|
||||
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
|
||||
paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
|
||||
if (paraEnd < 0) {
|
||||
paraEnd = bufEnd;
|
||||
} else {
|
||||
paraEnd++;
|
||||
}
|
||||
for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
|
||||
final int paraStart = premeasured.getParagraphStart(paraIndex);
|
||||
final int paraEnd = premeasured.getParagraphEnd(paraIndex);
|
||||
|
||||
int firstWidthLineCount = 1;
|
||||
int firstWidth = outerWidth;
|
||||
@@ -735,8 +749,7 @@ public class StaticLayout extends Layout {
|
||||
}
|
||||
}
|
||||
|
||||
measured = MeasuredText.buildForStaticLayout(
|
||||
paint, source, paraStart, paraEnd, textDir, measured);
|
||||
final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
|
||||
final char[] chs = measured.getChars();
|
||||
final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
|
||||
final int[] fmCache = measured.getFontMetrics().getRawArray();
|
||||
@@ -887,7 +900,8 @@ public class StaticLayout extends Layout {
|
||||
|
||||
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
|
||||
&& mLineCount < mMaximumVisibleLineCount) {
|
||||
measured = MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, measured);
|
||||
final MeasuredText measured =
|
||||
MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
|
||||
paint.getFontMetricsInt(fm);
|
||||
v = out(source,
|
||||
bufEnd, bufEnd, fm.ascent, fm.descent,
|
||||
@@ -901,9 +915,6 @@ public class StaticLayout extends Layout {
|
||||
ellipsizedWidth, 0, paint, false);
|
||||
}
|
||||
} finally {
|
||||
if (measured != null) {
|
||||
measured.recycle();
|
||||
}
|
||||
nFinish(nativePtr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.Layout;
|
||||
import android.text.ParcelableSpan;
|
||||
import android.text.PremeasuredText;
|
||||
import android.text.Selection;
|
||||
import android.text.SpanWatcher;
|
||||
import android.text.Spannable;
|
||||
@@ -5326,7 +5327,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (imm != null) imm.restartInput(this);
|
||||
} else if (type == BufferType.SPANNABLE || mMovement != null) {
|
||||
text = mSpannableFactory.newSpannable(text);
|
||||
} else if (!(text instanceof CharWrapper)) {
|
||||
} else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
|
||||
text = TextUtils.stringOrSpannedString(text);
|
||||
}
|
||||
|
||||
@@ -5610,10 +5611,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
spannable = (Spannable) text;
|
||||
} else {
|
||||
spannable = mSpannableFactory.newSpannable(text);
|
||||
text = spannable;
|
||||
}
|
||||
|
||||
SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
|
||||
if (spans.length == 0) {
|
||||
return text;
|
||||
} else {
|
||||
text = spannable;
|
||||
}
|
||||
|
||||
for (int i = 0; i < spans.length; i++) {
|
||||
spannable.removeSpan(spans[i]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user