Skip to content

Commit 282e377

Browse files
committed
Ellipsize marquee TextViews that aren't currently animating
On some devices the fading edge effect can be expensive. Offer an alternative for screens full of textviews. Change-Id: I0382b4ea0f8b0c6948cb68611f3679dbf5e2898a
1 parent 960ba91 commit 282e377

File tree

1 file changed

+159
-71
lines changed

1 file changed

+159
-71
lines changed

core/java/android/widget/TextView.java

Lines changed: 159 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import android.text.TextPaint;
6565
import android.text.TextUtils;
6666
import android.text.TextWatcher;
67+
import android.text.TextUtils.TruncateAt;
6768
import android.text.method.AllCapsTransformationMethod;
6869
import android.text.method.ArrowKeyMovementMethod;
6970
import android.text.method.DateKeyListener;
@@ -366,6 +367,35 @@ private static enum TextAlign {
366367

367368
private boolean mResolvedDrawables = false;
368369

370+
/**
371+
* On some devices the fading edges add a performance penalty if used
372+
* extensively in the same layout. This mode indicates how the marquee
373+
* is currently being shown, if applicable. (mEllipsize will == MARQUEE)
374+
*/
375+
private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
376+
377+
/**
378+
* When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
379+
* the layout that should be used when the mode switches.
380+
*/
381+
private Layout mSavedMarqueeModeLayout;
382+
383+
/**
384+
* Draw marquee text with fading edges as usual
385+
*/
386+
private static final int MARQUEE_FADE_NORMAL = 0;
387+
388+
/**
389+
* Draw marquee text as ellipsize end while inactive instead of with the fade.
390+
* (Useful for devices where the fade can be expensive if overdone)
391+
*/
392+
private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
393+
394+
/**
395+
* Draw marquee text with fading edges because it is currently active/animating.
396+
*/
397+
private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
398+
369399
/*
370400
* Kick-start the font cache for the zygote process (to pay the cost of
371401
* initializing freetype for our default font only once).
@@ -997,8 +1027,13 @@ public TextView(Context context,
9971027
setEllipsize(TextUtils.TruncateAt.END);
9981028
break;
9991029
case 4:
1000-
setHorizontalFadingEdgeEnabled(
1001-
ViewConfiguration.get(context).isFadingMarqueeEnabled());
1030+
if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1031+
setHorizontalFadingEdgeEnabled(true);
1032+
mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1033+
} else {
1034+
setHorizontalFadingEdgeEnabled(false);
1035+
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1036+
}
10021037
setEllipsize(TextUtils.TruncateAt.MARQUEE);
10031038
break;
10041039
}
@@ -3069,8 +3104,13 @@ private void setText(CharSequence text, BufferType type,
30693104

30703105
if (text instanceof Spanned &&
30713106
((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
3072-
setHorizontalFadingEdgeEnabled(
3073-
ViewConfiguration.get(mContext).isFadingMarqueeEnabled());
3107+
if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3108+
setHorizontalFadingEdgeEnabled(true);
3109+
mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3110+
} else {
3111+
setHorizontalFadingEdgeEnabled(false);
3112+
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3113+
}
30743114
setEllipsize(TextUtils.TruncateAt.MARQUEE);
30753115
}
30763116

@@ -4763,7 +4803,8 @@ protected void onDraw(Canvas canvas) {
47634803

47644804
final int layoutDirection = getResolvedLayoutDirection();
47654805
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
4766-
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4806+
if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
4807+
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
47674808
if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
47684809
(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
47694810
canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
@@ -5947,7 +5988,7 @@ private void nullLayouts() {
59475988
mSavedHintLayout = (BoringLayout) mHintLayout;
59485989
}
59495990

5950-
mLayout = mHintLayout = null;
5991+
mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
59515992

59525993
// Since it depends on the value of mLayout
59535994
prepareCursorControllers();
@@ -6067,73 +6108,25 @@ protected void makeNewLayout(int w, int hintWidth,
60676108

60686109
Layout.Alignment alignment = getLayoutAlignment();
60696110
boolean shouldEllipsize = mEllipsize != null && mInput == null;
6111+
final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6112+
mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6113+
TruncateAt effectiveEllipsize = mEllipsize;
6114+
if (mEllipsize == TruncateAt.MARQUEE &&
6115+
mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6116+
effectiveEllipsize = TruncateAt.END;
6117+
}
60706118

60716119
if (mTextDir == null) {
60726120
resolveTextDirection();
60736121
}
6074-
if (mText instanceof Spannable) {
6075-
mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
6076-
alignment, mTextDir, mSpacingMult,
6077-
mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
6078-
ellipsisWidth);
6079-
} else {
6080-
if (boring == UNKNOWN_BORING) {
6081-
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6082-
if (boring != null) {
6083-
mBoring = boring;
6084-
}
6085-
}
60866122

6087-
if (boring != null) {
6088-
if (boring.width <= w &&
6089-
(mEllipsize == null || boring.width <= ellipsisWidth)) {
6090-
if (mSavedLayout != null) {
6091-
mLayout = mSavedLayout.
6092-
replaceOrMake(mTransformed, mTextPaint,
6093-
w, alignment, mSpacingMult, mSpacingAdd,
6094-
boring, mIncludePad);
6095-
} else {
6096-
mLayout = BoringLayout.make(mTransformed, mTextPaint,
6097-
w, alignment, mSpacingMult, mSpacingAdd,
6098-
boring, mIncludePad);
6099-
}
6100-
6101-
mSavedLayout = (BoringLayout) mLayout;
6102-
} else if (shouldEllipsize && boring.width <= w) {
6103-
if (mSavedLayout != null) {
6104-
mLayout = mSavedLayout.
6105-
replaceOrMake(mTransformed, mTextPaint,
6106-
w, alignment, mSpacingMult, mSpacingAdd,
6107-
boring, mIncludePad, mEllipsize,
6108-
ellipsisWidth);
6109-
} else {
6110-
mLayout = BoringLayout.make(mTransformed, mTextPaint,
6111-
w, alignment, mSpacingMult, mSpacingAdd,
6112-
boring, mIncludePad, mEllipsize,
6113-
ellipsisWidth);
6114-
}
6115-
} else if (shouldEllipsize) {
6116-
mLayout = new StaticLayout(mTransformed,
6117-
0, mTransformed.length(),
6118-
mTextPaint, w, alignment, mTextDir, mSpacingMult,
6119-
mSpacingAdd, mIncludePad, mEllipsize,
6120-
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6121-
} else {
6122-
mLayout = new StaticLayout(mTransformed, mTextPaint,
6123-
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6124-
mIncludePad);
6125-
}
6126-
} else if (shouldEllipsize) {
6127-
mLayout = new StaticLayout(mTransformed,
6128-
0, mTransformed.length(),
6129-
mTextPaint, w, alignment, mTextDir, mSpacingMult,
6130-
mSpacingAdd, mIncludePad, mEllipsize,
6131-
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6132-
} else {
6133-
mLayout = new StaticLayout(mTransformed, mTextPaint,
6134-
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6135-
mIncludePad);
6136-
}
6123+
mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize,
6124+
effectiveEllipsize, effectiveEllipsize == mEllipsize);
6125+
if (switchEllipsize) {
6126+
TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6127+
TruncateAt.END : TruncateAt.MARQUEE;
6128+
mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment,
6129+
shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
61376130
}
61386131

61396132
shouldEllipsize = mEllipsize != null;
@@ -6224,6 +6217,77 @@ protected void makeNewLayout(int w, int hintWidth,
62246217
prepareCursorControllers();
62256218
}
62266219

6220+
private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth,
6221+
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6222+
boolean useSaved) {
6223+
Layout result = null;
6224+
if (mText instanceof Spannable) {
6225+
result = new DynamicLayout(mText, mTransformed, mTextPaint, w,
6226+
alignment, mTextDir, mSpacingMult,
6227+
mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
6228+
ellipsisWidth);
6229+
} else {
6230+
if (boring == UNKNOWN_BORING) {
6231+
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6232+
if (boring != null) {
6233+
mBoring = boring;
6234+
}
6235+
}
6236+
6237+
if (boring != null) {
6238+
if (boring.width <= w &&
6239+
(effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6240+
if (useSaved && mSavedLayout != null) {
6241+
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6242+
w, alignment, mSpacingMult, mSpacingAdd,
6243+
boring, mIncludePad);
6244+
} else {
6245+
result = BoringLayout.make(mTransformed, mTextPaint,
6246+
w, alignment, mSpacingMult, mSpacingAdd,
6247+
boring, mIncludePad);
6248+
}
6249+
6250+
if (useSaved) {
6251+
mSavedLayout = (BoringLayout) result;
6252+
}
6253+
} else if (shouldEllipsize && boring.width <= w) {
6254+
if (useSaved && mSavedLayout != null) {
6255+
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6256+
w, alignment, mSpacingMult, mSpacingAdd,
6257+
boring, mIncludePad, effectiveEllipsize,
6258+
ellipsisWidth);
6259+
} else {
6260+
result = BoringLayout.make(mTransformed, mTextPaint,
6261+
w, alignment, mSpacingMult, mSpacingAdd,
6262+
boring, mIncludePad, effectiveEllipsize,
6263+
ellipsisWidth);
6264+
}
6265+
} else if (shouldEllipsize) {
6266+
result = new StaticLayout(mTransformed,
6267+
0, mTransformed.length(),
6268+
mTextPaint, w, alignment, mTextDir, mSpacingMult,
6269+
mSpacingAdd, mIncludePad, effectiveEllipsize,
6270+
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6271+
} else {
6272+
result = new StaticLayout(mTransformed, mTextPaint,
6273+
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6274+
mIncludePad);
6275+
}
6276+
} else if (shouldEllipsize) {
6277+
result = new StaticLayout(mTransformed,
6278+
0, mTransformed.length(),
6279+
mTextPaint, w, alignment, mTextDir, mSpacingMult,
6280+
mSpacingAdd, mIncludePad, effectiveEllipsize,
6281+
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6282+
} else {
6283+
result = new StaticLayout(mTransformed, mTextPaint,
6284+
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6285+
mIncludePad);
6286+
}
6287+
}
6288+
return result;
6289+
}
6290+
62276291
private boolean compressText(float width) {
62286292
if (isHardwareAccelerated()) return false;
62296293

@@ -7179,7 +7243,9 @@ private boolean isCursorVisible() {
71797243

71807244
private boolean canMarquee() {
71817245
int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7182-
return width > 0 && mLayout.getLineWidth(0) > width;
7246+
return width > 0 && (mLayout.getLineWidth(0) > width ||
7247+
(mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7248+
mSavedMarqueeModeLayout.getLineWidth(0) > width));
71837249
}
71847250

71857251
private void startMarquee() {
@@ -7193,6 +7259,16 @@ private void startMarquee() {
71937259
if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
71947260
getLineCount() == 1 && canMarquee()) {
71957261

7262+
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7263+
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7264+
final Layout tmp = mLayout;
7265+
mLayout = mSavedMarqueeModeLayout;
7266+
mSavedMarqueeModeLayout = tmp;
7267+
setHorizontalFadingEdgeEnabled(true);
7268+
requestLayout();
7269+
invalidate();
7270+
}
7271+
71967272
if (mMarquee == null) mMarquee = new Marquee(this);
71977273
mMarquee.start(mMarqueeRepeatLimit);
71987274
}
@@ -7202,6 +7278,16 @@ private void stopMarquee() {
72027278
if (mMarquee != null && !mMarquee.isStopped()) {
72037279
mMarquee.stop();
72047280
}
7281+
7282+
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7283+
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7284+
final Layout tmp = mSavedMarqueeModeLayout;
7285+
mSavedMarqueeModeLayout = mLayout;
7286+
mLayout = tmp;
7287+
setHorizontalFadingEdgeEnabled(false);
7288+
requestLayout();
7289+
invalidate();
7290+
}
72057291
}
72067292

72077293
private void startStopMarquee(boolean start) {
@@ -8407,7 +8493,8 @@ private void makeBlink() {
84078493
@Override
84088494
protected float getLeftFadingEdgeStrength() {
84098495
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8410-
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8496+
if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8497+
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
84118498
if (mMarquee != null && !mMarquee.isStopped()) {
84128499
final Marquee marquee = mMarquee;
84138500
if (marquee.shouldDrawLeftFade()) {
@@ -8436,7 +8523,8 @@ protected float getLeftFadingEdgeStrength() {
84368523
@Override
84378524
protected float getRightFadingEdgeStrength() {
84388525
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8439-
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8526+
if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8527+
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
84408528
if (mMarquee != null && !mMarquee.isStopped()) {
84418529
final Marquee marquee = mMarquee;
84428530
return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();

0 commit comments

Comments
 (0)