Fix ellipsizing and text scale x calculation

Bug: 17742721
Bug: 11158757
Change-Id: I98b55a8e661804e84f75e75675c6f2ef33ac1a11
This commit is contained in:
Tadashi G. Takaoka 2014-10-02 16:22:06 +09:00
parent 45ebb2001d
commit 049d642cc4

View file

@ -60,6 +60,9 @@ import com.android.inputmethod.latin.utils.ViewLayoutUtils;
import java.util.ArrayList; import java.util.ArrayList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
final class SuggestionStripLayoutHelper { final class SuggestionStripLayoutHelper {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
@ -213,15 +216,14 @@ final class SuggestionStripLayoutHelper {
return word; return word;
} }
final int len = word.length();
final Spannable spannedWord = new SpannableString(word); final Spannable spannedWord = new SpannableString(word);
final int options = mSuggestionStripOptions; final int options = mSuggestionStripOptions;
if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0) if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0)
|| (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) { || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) {
spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); addStyleSpan(spannedWord, BOLD_SPAN);
} }
if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) { if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) {
spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); addStyleSpan(spannedWord, UNDERLINE_SPAN);
} }
return spannedWord; return spannedWord;
} }
@ -446,10 +448,11 @@ final class SuggestionStripLayoutHelper {
// {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack. // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
// Use a simple {@link String} to avoid the issue. // Use a simple {@link String} to avoid the issue.
wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString()); wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString());
final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); final CharSequence text = getEllipsizedTextWithSettingScaleX(
final float scaleX = getTextScaleX(word, width, wordView.getPaint()); word, width, wordView.getPaint());
final float scaleX = wordView.getTextScaleX();
wordView.setText(text); // TextView.setText() resets text scale x to 1.0. wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE)); wordView.setTextScaleX(scaleX);
// A <code>wordView</code> should be disabled when <code>word</code> is empty in order to // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to
// make it unclickable. // make it unclickable.
// With accessibility touch exploration on, <code>wordView</code> should be enabled even // With accessibility touch exploration on, <code>wordView</code> should be enabled even
@ -562,7 +565,8 @@ final class SuggestionStripLayoutHelper {
final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save); final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save);
wordView.setTextColor(mColorTypedWord); wordView.setTextColor(mColorTypedWord);
final int wordWidth = (int)(width * mCenterSuggestionWeight); final int wordWidth = (int)(width * mCenterSuggestionWeight);
final CharSequence wordToSave = getEllipsizedText(word, wordWidth, wordView.getPaint()); final CharSequence wordToSave = getEllipsizedTextWithSettingScaleX(
word, wordWidth, wordView.getPaint());
final float wordScaleX = wordView.getTextScaleX(); final float wordScaleX = wordView.getTextScaleX();
wordView.setText(wordToSave); wordView.setText(wordToSave);
wordView.setTextScaleX(wordScaleX); wordView.setTextScaleX(wordScaleX);
@ -596,7 +600,7 @@ final class SuggestionStripLayoutHelper {
} }
hintView.setTextColor(mColorAutoCorrect); hintView.setTextColor(mColorAutoCorrect);
final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
hintView.setText(hintText); hintView.setText(hintText); // TextView.setText() resets text scale x to 1.0.
hintView.setTextScaleX(hintScaleX); hintView.setTextScaleX(hintScaleX);
setLayoutWeight(hintView, hintWeight, ViewGroup.LayoutParams.MATCH_PARENT); setLayoutWeight(hintView, hintWeight, ViewGroup.LayoutParams.MATCH_PARENT);
} }
@ -608,8 +612,7 @@ final class SuggestionStripLayoutHelper {
final int width = titleView.getWidth() - titleView.getPaddingLeft() final int width = titleView.getWidth() - titleView.getPaddingLeft()
- titleView.getPaddingRight(); - titleView.getPaddingRight();
titleView.setTextColor(mColorAutoCorrect); titleView.setTextColor(mColorAutoCorrect);
titleView.setText(importantNoticeTitle); titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0.
titleView.setTextScaleX(1.0f); // Reset textScaleX.
final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint()); final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint());
titleView.setTextScaleX(titleScaleX); titleView.setTextScaleX(titleScaleX);
} }
@ -624,7 +627,7 @@ final class SuggestionStripLayoutHelper {
} }
} }
private static float getTextScaleX(final CharSequence text, final int maxWidth, private static float getTextScaleX(@Nullable final CharSequence text, final int maxWidth,
final TextPaint paint) { final TextPaint paint) {
paint.setTextScaleX(1.0f); paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint); final int width = getTextWidth(text, paint);
@ -634,8 +637,9 @@ final class SuggestionStripLayoutHelper {
return maxWidth / (float) width; return maxWidth / (float) width;
} }
private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, @Nullable
final TextPaint paint) { private static CharSequence getEllipsizedTextWithSettingScaleX(
@Nullable final CharSequence text, final int maxWidth, @Nonnull final TextPaint paint) {
if (text == null) { if (text == null) {
return null; return null;
} }
@ -645,62 +649,63 @@ final class SuggestionStripLayoutHelper {
return text; return text;
} }
// Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To // <code>text</code> must be ellipsized with minimum text scale x.
// get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
final float upscaledWidth = maxWidth / MIN_TEXT_XSCALE;
CharSequence ellipsized = TextUtils.ellipsize(
text, paint, upscaledWidth, TextUtils.TruncateAt.MIDDLE);
// For an unknown reason, ellipsized seems to return a text that does indeed fit inside the
// passed width according to paint.measureText, but not according to paint.getTextWidths.
// But when rendered, the text seems to actually take up as many pixels as returned by
// paint.getTextWidths, hence problem.
// To save this case, we compare the measured size of the new text, and if it's too much,
// try it again removing the difference. This may still give a text too long by one or
// two pixels so we take an additional 2 pixels cushion and call it a day.
// TODO: figure out why getTextWidths and measureText don't agree with each other, and
// remove the following code.
final float ellipsizedTextWidth = getTextWidth(ellipsized, paint);
if (upscaledWidth <= ellipsizedTextWidth) {
ellipsized = TextUtils.ellipsize(
text, paint, upscaledWidth - (ellipsizedTextWidth - upscaledWidth) - 2,
TextUtils.TruncateAt.MIDDLE);
}
paint.setTextScaleX(MIN_TEXT_XSCALE); paint.setTextScaleX(MIN_TEXT_XSCALE);
return ellipsized; final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN);
final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN);
// TextUtils.ellipsize erases any span object existed after ellipsized point.
// We have to restore these spans afterward.
final CharSequence ellipsizedText = TextUtils.ellipsize(
text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE);
if (!hasBoldStyle && !hasUnderlineStyle) {
return ellipsizedText;
}
final Spannable spannableText = (ellipsizedText instanceof Spannable)
? (Spannable)ellipsizedText : new SpannableString(ellipsizedText);
if (hasBoldStyle) {
addStyleSpan(spannableText, BOLD_SPAN);
}
if (hasUnderlineStyle) {
addStyleSpan(spannableText, UNDERLINE_SPAN);
}
return spannableText;
} }
private static int getTextWidth(final CharSequence text, final TextPaint paint) { private static boolean hasStyleSpan(@Nullable final CharSequence text,
final CharacterStyle style) {
if (text instanceof Spanned) {
return ((Spanned)text).getSpanStart(style) >= 0;
}
return false;
}
private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) {
text.removeSpan(style);
text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) {
if (TextUtils.isEmpty(text)) { if (TextUtils.isEmpty(text)) {
return 0; return 0;
} }
final int length = text.length();
final float[] widths = new float[length];
final int count;
final Typeface savedTypeface = paint.getTypeface(); final Typeface savedTypeface = paint.getTypeface();
try {
paint.setTypeface(getTextTypeface(text)); paint.setTypeface(getTextTypeface(text));
final int len = text.length(); count = paint.getTextWidths(text, 0, length, widths);
final float[] widths = new float[len]; } finally {
final int count = paint.getTextWidths(text, 0, len, widths); paint.setTypeface(savedTypeface);
}
int width = 0; int width = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
width += Math.round(widths[i] + 0.5f); width += Math.round(widths[i] + 0.5f);
} }
paint.setTypeface(savedTypeface);
return width; return width;
} }
private static Typeface getTextTypeface(final CharSequence text) { private static Typeface getTextTypeface(@Nullable final CharSequence text) {
if (!(text instanceof SpannableString)) { return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
return Typeface.DEFAULT;
}
final SpannableString ss = (SpannableString)text;
final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
if (styles.length == 0) {
return Typeface.DEFAULT;
}
if (styles[0].getStyle() == Typeface.BOLD) {
return Typeface.DEFAULT_BOLD;
}
// TODO: BOLD_ITALIC, ITALIC case?
return Typeface.DEFAULT;
} }
} }