6464import android .text .TextPaint ;
6565import android .text .TextUtils ;
6666import android .text .TextWatcher ;
67+ import android .text .TextUtils .TruncateAt ;
6768import android .text .method .AllCapsTransformationMethod ;
6869import android .text .method .ArrowKeyMovementMethod ;
6970import 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