@@ -131,6 +131,7 @@ public final class CollapsingTextHelper {
131131 private float currentShadowDx ;
132132 private float currentShadowDy ;
133133 private int currentShadowColor ;
134+ private int currentMaxLines ;
134135
135136 private int [] state ;
136137
@@ -161,11 +162,16 @@ public final class CollapsingTextHelper {
161162 private float collapsedTextBlend ;
162163 private float expandedTextBlend ;
163164 private CharSequence textToDrawCollapsed ;
164- private int maxLines = 1 ;
165+
166+ private static final int ONE_LINE = 1 ;
167+ private int expandedMaxLines = ONE_LINE ;
168+ private int collapsedMaxLines = ONE_LINE ;
165169 private float lineSpacingAdd = StaticLayoutBuilderCompat .DEFAULT_LINE_SPACING_ADD ;
166170 private float lineSpacingMultiplier = StaticLayoutBuilderCompat .DEFAULT_LINE_SPACING_MULTIPLIER ;
167171 private int hyphenationFrequency = StaticLayoutBuilderCompat .DEFAULT_HYPHENATION_FREQUENCY ;
168172 @ Nullable private StaticLayoutBuilderConfigurer staticLayoutBuilderConfigurer ;
173+ private int collapsedHeight = -1 ;
174+ private int expandedHeight = -1 ;
169175
170176 public CollapsingTextHelper (View view ) {
171177 this .view = view ;
@@ -181,6 +187,13 @@ public CollapsingTextHelper(View view) {
181187 maybeUpdateFontWeightAdjustment (view .getContext ().getResources ().getConfiguration ());
182188 }
183189
190+ public void setCollapsedMaxLines (int collapsedMaxLines ) {
191+ if (collapsedMaxLines != this .collapsedMaxLines ) {
192+ this .collapsedMaxLines = collapsedMaxLines ;
193+ recalculate ();
194+ }
195+ }
196+
184197 public void setTextSizeInterpolator (TimeInterpolator interpolator ) {
185198 textSizeInterpolator = interpolator ;
186199 recalculate ();
@@ -261,13 +274,27 @@ public void setCollapsedBounds(@NonNull Rect bounds) {
261274 setCollapsedBounds (bounds .left , bounds .top , bounds .right , bounds .bottom );
262275 }
263276
264- public void getCollapsedTextActualBounds (@ NonNull RectF bounds , int labelWidth , int textGravity ) {
277+ public void getCollapsedTextBottomTextBounds (
278+ @ NonNull RectF bounds , int labelWidth , int textGravity ) {
265279 isRtl = calculateIsRtl (text );
266280 bounds .left = max (getCollapsedTextLeftBound (labelWidth , textGravity ), collapsedBounds .left );
267281 bounds .top = collapsedBounds .top ;
268282 bounds .right =
269283 min (getCollapsedTextRightBound (bounds , labelWidth , textGravity ), collapsedBounds .right );
270284 bounds .bottom = collapsedBounds .top + getCollapsedTextHeight ();
285+ if (textLayout != null && !shouldTruncateCollapsedToSingleLine ()) {
286+ // If the text is not truncated to one line when collapsed, we want to return the width of the
287+ // bottommost line, which is the textLayout's line width * the scale factor of the expanded
288+ // text size to the collapsed text size.
289+ float lineWidth =
290+ textLayout .getLineWidth (textLayout .getLineCount () - 1 )
291+ * (collapsedTextSize / expandedTextSize );
292+ if (isRtl ) {
293+ bounds .left = bounds .right - lineWidth ;
294+ } else {
295+ bounds .right = bounds .left + lineWidth ;
296+ }
297+ }
271298 }
272299
273300 private float getCollapsedTextLeftBound (int width , int gravity ) {
@@ -294,19 +321,45 @@ private float getCollapsedTextRightBound(@NonNull RectF bounds, int width, int g
294321 }
295322 }
296323
297- public float getExpandedTextHeight () {
324+ public float getExpandedTextSingleLineHeight () {
298325 getTextPaintExpanded (tmpPaint );
299326 // Return expanded height measured from the baseline.
300327 return -tmpPaint .ascent ();
301328 }
302329
303- public float getExpandedTextFullHeight () {
330+ public float getExpandedTextFullSingleLineHeight () {
304331 getTextPaintExpanded (tmpPaint );
305332 // Return expanded height measured from the baseline.
306333 return -tmpPaint .ascent () + tmpPaint .descent ();
307334 }
308335
336+ public void updateTextHeights (int availableWidth ) {
337+ // Set collapsed height
338+ getTextPaintCollapsed (tmpPaint );
339+ StaticLayout textLayout =
340+ createStaticLayout (
341+ collapsedMaxLines ,
342+ tmpPaint ,
343+ text ,
344+ availableWidth * (collapsedTextSize / expandedTextSize ),
345+ isRtl );
346+ collapsedHeight = textLayout .getHeight ();
347+
348+ // Set expanded height
349+ getTextPaintExpanded (tmpPaint );
350+ textLayout = createStaticLayout (expandedMaxLines , tmpPaint , text , availableWidth , isRtl );
351+ expandedHeight = textLayout .getHeight ();
352+ }
353+
309354 public float getCollapsedTextHeight () {
355+ return collapsedHeight != -1 ? collapsedHeight : getCollapsedSingleLineHeight ();
356+ }
357+
358+ public float getExpandedTextHeight () {
359+ return expandedHeight != -1 ? expandedHeight : getExpandedTextSingleLineHeight ();
360+ }
361+
362+ public float getCollapsedSingleLineHeight () {
310363 getTextPaintCollapsed (tmpPaint );
311364 // Return collapsed height measured from the baseline.
312365 return -tmpPaint .ascent ();
@@ -707,12 +760,18 @@ private int getCurrentColor(@Nullable ColorStateList colorStateList) {
707760 return colorStateList .getDefaultColor ();
708761 }
709762
763+ private boolean shouldTruncateCollapsedToSingleLine () {
764+ return collapsedMaxLines == ONE_LINE ;
765+ }
766+
710767 private void calculateBaseOffsets (boolean forceRecalculate ) {
711768 // We then calculate the collapsed text size, using the same logic
712769 calculateUsingTextSize (/* fraction= */ 1 , forceRecalculate );
713770 if (textToDraw != null && textLayout != null ) {
714- textToDrawCollapsed =
715- TextUtils .ellipsize (textToDraw , textPaint , textLayout .getWidth (), titleTextEllipsize );
771+ textToDrawCollapsed = shouldTruncateCollapsedToSingleLine ()
772+ ? TextUtils .ellipsize (
773+ textToDraw , textPaint , textLayout .getWidth (), titleTextEllipsize )
774+ : textToDraw ;
716775 }
717776 if (textToDrawCollapsed != null ) {
718777 collapsedTextWidth = measureTextWidth (textPaint , textToDrawCollapsed );
@@ -754,7 +813,7 @@ private void calculateBaseOffsets(boolean forceRecalculate) {
754813 calculateUsingTextSize (/* fraction= */ 0 , forceRecalculate );
755814 float expandedTextHeight = textLayout != null ? textLayout .getHeight () : 0 ;
756815 float expandedTextWidth = 0 ;
757- if (textLayout != null && maxLines > 1 ) {
816+ if (textLayout != null && expandedMaxLines > 1 ) {
758817 expandedTextWidth = textLayout .getWidth ();
759818 } else if (textToDraw != null ) {
760819 expandedTextWidth = measureTextWidth (textPaint , textToDraw );
@@ -847,6 +906,7 @@ public void draw(@NonNull Canvas canvas) {
847906 }
848907
849908 if (shouldDrawMultiline ()
909+ && shouldTruncateCollapsedToSingleLine ()
850910 && (!fadeModeEnabled || expandedFraction > fadeModeThresholdFraction )) {
851911 drawMultilineTransition (canvas , currentDrawX - textLayout .getLineStart (0 ), y );
852912 } else {
@@ -859,7 +919,7 @@ public void draw(@NonNull Canvas canvas) {
859919 }
860920
861921 private boolean shouldDrawMultiline () {
862- return maxLines > 1 && (!isRtl || fadeModeEnabled );
922+ return ( expandedMaxLines > 1 || collapsedMaxLines > 1 ) && (!isRtl || fadeModeEnabled );
863923 }
864924
865925 private void drawMultilineTransition (@ NonNull Canvas canvas , float currentExpandedX , float y ) {
@@ -975,11 +1035,16 @@ private void calculateUsingTextSize(final float fraction, boolean forceRecalcula
9751035 Typeface newTypeface ;
9761036
9771037 if (isClose (fraction , /* targetValue= */ 1 )) {
978- newTextSize = collapsedTextSize ;
979- newLetterSpacing = collapsedLetterSpacing ;
980- scale = 1f ;
1038+ newTextSize = shouldTruncateCollapsedToSingleLine () ? collapsedTextSize : expandedTextSize ;
1039+ newLetterSpacing =
1040+ shouldTruncateCollapsedToSingleLine () ? collapsedLetterSpacing : expandedLetterSpacing ;
1041+ scale =
1042+ shouldTruncateCollapsedToSingleLine ()
1043+ ? 1f
1044+ : lerp (expandedTextSize , collapsedTextSize , fraction , textSizeInterpolator )
1045+ / expandedTextSize ;
1046+ availableWidth = shouldTruncateCollapsedToSingleLine () ? collapsedWidth : expandedWidth ;
9811047 newTypeface = collapsedTypeface ;
982- availableWidth = collapsedWidth ;
9831048 } else {
9841049 newTextSize = expandedTextSize ;
9851050 newLetterSpacing = expandedLetterSpacing ;
@@ -1010,30 +1075,38 @@ private void calculateUsingTextSize(final float fraction, boolean forceRecalcula
10101075 // cap the available width so that when the expanded text scales down, it matches
10111076 // the collapsed width
10121077 // Otherwise we'll just use the expanded width
1013-
1078+ // If we are not truncating the collapsed text, when we are always scaling the expanded
1079+ // text, so we will always use the expanded width as the available width
10141080 availableWidth =
1015- scaledDownWidth > collapsedWidth
1081+ scaledDownWidth > collapsedWidth && shouldTruncateCollapsedToSingleLine ()
10161082 ? min (collapsedWidth / textSizeRatio , expandedWidth )
10171083 : expandedWidth ;
10181084 }
10191085 }
10201086
1087+ // Swap between the expanded and collapsed max lines depending on whether or not we're closer
1088+ // to being expanded or collapsed.
1089+ int maxLines = fraction < 0.5f ? expandedMaxLines : collapsedMaxLines ;
1090+
10211091 boolean updateDrawText ;
10221092 if (availableWidth > 0 ) {
10231093 boolean textSizeChanged = currentTextSize != newTextSize ;
10241094 boolean letterSpacingChanged = currentLetterSpacing != newLetterSpacing ;
10251095 boolean typefaceChanged = currentTypeface != newTypeface ;
10261096 boolean availableWidthChanged = textLayout != null && availableWidth != textLayout .getWidth ();
1097+ boolean maxLinesChanged = currentMaxLines != maxLines ;
10271098 updateDrawText =
10281099 textSizeChanged
10291100 || letterSpacingChanged
10301101 || availableWidthChanged
10311102 || typefaceChanged
1103+ || maxLinesChanged
10321104 || boundsChanged ;
10331105 currentTextSize = newTextSize ;
10341106 currentLetterSpacing = newLetterSpacing ;
10351107 currentTypeface = newTypeface ;
10361108 boundsChanged = false ;
1109+ currentMaxLines = maxLines ;
10371110 // Use linear text scaling if we're scaling the canvas
10381111 textPaint .setLinearText (scale != 1f );
10391112 } else {
@@ -1046,12 +1119,19 @@ private void calculateUsingTextSize(final float fraction, boolean forceRecalcula
10461119 textPaint .setLetterSpacing (currentLetterSpacing );
10471120
10481121 isRtl = calculateIsRtl (text );
1049- textLayout = createStaticLayout (shouldDrawMultiline () ? maxLines : 1 , availableWidth , isRtl );
1122+ textLayout =
1123+ createStaticLayout (
1124+ shouldDrawMultiline () ? maxLines : 1 ,
1125+ textPaint ,
1126+ text ,
1127+ availableWidth * (shouldTruncateCollapsedToSingleLine () ? 1 : scale ),
1128+ isRtl );
10501129 textToDraw = textLayout .getText ();
10511130 }
10521131 }
10531132
1054- private StaticLayout createStaticLayout (int maxLines , float availableWidth , boolean isRtl ) {
1133+ private StaticLayout createStaticLayout (
1134+ int maxLines , TextPaint textPaint , CharSequence text , float availableWidth , boolean isRtl ) {
10551135 StaticLayout textLayout = null ;
10561136 try {
10571137 // In multiline mode, the text alignment should be controlled by the static layout.
@@ -1120,15 +1200,15 @@ public CharSequence getText() {
11201200 return text ;
11211201 }
11221202
1123- public void setMaxLines (int maxLines ) {
1124- if (maxLines != this .maxLines ) {
1125- this .maxLines = maxLines ;
1203+ public void setExpandedMaxLines (int expandedMaxLines ) {
1204+ if (expandedMaxLines != this .expandedMaxLines ) {
1205+ this .expandedMaxLines = expandedMaxLines ;
11261206 recalculate ();
11271207 }
11281208 }
11291209
1130- public int getMaxLines () {
1131- return maxLines ;
1210+ public int getExpandedMaxLines () {
1211+ return expandedMaxLines ;
11321212 }
11331213
11341214 /**
0 commit comments