5656import androidx .core .view .GravityCompat ;
5757import androidx .core .view .ViewCompat ;
5858import com .google .android .material .animation .AnimationUtils ;
59+ import com .google .android .material .color .MaterialColors ;
5960import com .google .android .material .internal .StaticLayoutBuilderCompat .StaticLayoutBuilderCompatException ;
6061import com .google .android .material .resources .CancelableFontCallback ;
6162import com .google .android .material .resources .CancelableFontCallback .ApplyFont ;
@@ -136,6 +137,10 @@ public final class CollapsingTextHelper {
136137
137138 private float scale ;
138139 private float currentTextSize ;
140+ private float currentShadowRadius ;
141+ private float currentShadowDx ;
142+ private float currentShadowDy ;
143+ private int currentShadowColor ;
139144
140145 private int [] state ;
141146
@@ -471,11 +476,11 @@ private boolean setCollapsedTypefaceInternal(Typeface typeface) {
471476 }
472477 if (collapsedTypefaceDefault != typeface ) {
473478 collapsedTypefaceDefault = typeface ;
474- collapsedTypefaceBold = TypefaceUtils . maybeCopyWithFontWeightAdjustment (
475- view . getContext (). getResources (). getConfiguration (),
476- typeface );
477- collapsedTypeface = collapsedTypefaceBold == null
478- ? collapsedTypefaceDefault : collapsedTypefaceBold ;
479+ collapsedTypefaceBold =
480+ TypefaceUtils . maybeCopyWithFontWeightAdjustment (
481+ view . getContext (). getResources (). getConfiguration (), typeface );
482+ collapsedTypeface =
483+ collapsedTypefaceBold == null ? collapsedTypefaceDefault : collapsedTypefaceBold ;
479484 return true ;
480485 }
481486 return false ;
@@ -490,11 +495,11 @@ private boolean setExpandedTypefaceInternal(Typeface typeface) {
490495 }
491496 if (expandedTypefaceDefault != typeface ) {
492497 expandedTypefaceDefault = typeface ;
493- expandedTypefaceBold = TypefaceUtils . maybeCopyWithFontWeightAdjustment (
494- view . getContext (). getResources (). getConfiguration (),
495- typeface );
496- expandedTypeface = expandedTypefaceBold == null
497- ? expandedTypefaceDefault : expandedTypefaceBold ;
498+ expandedTypefaceBold =
499+ TypefaceUtils . maybeCopyWithFontWeightAdjustment (
500+ view . getContext (). getResources (). getConfiguration (), typeface );
501+ expandedTypeface =
502+ expandedTypefaceBold == null ? expandedTypefaceDefault : expandedTypefaceBold ;
498503 return true ;
499504 }
500505 return false ;
@@ -511,20 +516,19 @@ public Typeface getExpandedTypeface() {
511516 public void maybeUpdateFontWeightAdjustment (@ NonNull Configuration configuration ) {
512517 if (VERSION .SDK_INT >= VERSION_CODES .S ) {
513518 if (collapsedTypefaceDefault != null ) {
514- collapsedTypefaceBold = TypefaceUtils . maybeCopyWithFontWeightAdjustment (
515- configuration ,
516- collapsedTypefaceDefault );
519+ collapsedTypefaceBold =
520+ TypefaceUtils . maybeCopyWithFontWeightAdjustment (
521+ configuration , collapsedTypefaceDefault );
517522 }
518523 if (expandedTypefaceDefault != null ) {
519- expandedTypefaceBold = TypefaceUtils .maybeCopyWithFontWeightAdjustment (
520- configuration ,
521- expandedTypefaceDefault );
524+ expandedTypefaceBold =
525+ TypefaceUtils .maybeCopyWithFontWeightAdjustment (configuration , expandedTypefaceDefault );
522526 }
523- collapsedTypeface = collapsedTypefaceBold != null
524- ? collapsedTypefaceBold : collapsedTypefaceDefault ;
525- expandedTypeface = expandedTypefaceBold != null
526- ? expandedTypefaceBold : expandedTypefaceDefault ;
527- recalculate (/* forceRecalculate= */ true );
527+ collapsedTypeface =
528+ collapsedTypefaceBold != null ? collapsedTypefaceBold : collapsedTypefaceDefault ;
529+ expandedTypeface =
530+ expandedTypefaceBold != null ? expandedTypefaceBold : expandedTypefaceDefault ;
531+ recalculate (/* forceRecalculate= */ true );
528532 }
529533 }
530534
@@ -621,7 +625,7 @@ private void calculateOffsets(final float fraction) {
621625 // If the collapsed and expanded text colors are different, blend them based on the
622626 // fraction
623627 textPaint .setColor (
624- blendColors (
628+ blendARGB (
625629 getCurrentExpandedTextColor (), getCurrentCollapsedTextColor (), textBlendFraction ));
626630 } else {
627631 textPaint .setColor (getCurrentCollapsedTextColor ());
@@ -640,12 +644,15 @@ private void calculateOffsets(final float fraction) {
640644 }
641645 }
642646
647+ // Calculates paint parameters for shadow layer.
648+ currentShadowRadius = lerp (expandedShadowRadius , collapsedShadowRadius , fraction , null );
649+ currentShadowDx = lerp (expandedShadowDx , collapsedShadowDx , fraction , null );
650+ currentShadowDy = lerp (expandedShadowDy , collapsedShadowDy , fraction , null );
651+ currentShadowColor =
652+ blendARGB (
653+ getCurrentColor (expandedShadowColor ), getCurrentColor (collapsedShadowColor ), fraction );
643654 textPaint .setShadowLayer (
644- lerp (expandedShadowRadius , collapsedShadowRadius , fraction , null ),
645- lerp (expandedShadowDx , collapsedShadowDx , fraction , null ),
646- lerp (expandedShadowDy , collapsedShadowDy , fraction , null ),
647- blendColors (
648- getCurrentColor (expandedShadowColor ), getCurrentColor (collapsedShadowColor ), fraction ));
655+ currentShadowRadius , currentShadowDx , currentShadowDy , currentShadowColor );
649656
650657 if (fadeModeEnabled ) {
651658 int originalAlpha = textPaint .getAlpha ();
@@ -659,8 +666,7 @@ private void calculateOffsets(final float fraction) {
659666 ViewCompat .postInvalidateOnAnimation (view );
660667 }
661668
662- private float calculateFadeModeTextAlpha (
663- @ FloatRange (from = 0.0 , to = 1.0 ) float fraction ) {
669+ private float calculateFadeModeTextAlpha (@ FloatRange (from = 0.0 , to = 1.0 ) float fraction ) {
664670 if (fraction <= fadeModeThresholdFraction ) {
665671 return AnimationUtils .lerp (
666672 /* startValue= */ 1 ,
@@ -870,10 +876,29 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
870876 canvas .translate (currentExpandedX , y );
871877 // Expanded text
872878 textPaint .setAlpha ((int ) (expandedTextBlend * originalAlpha ));
879+ // Workaround for API 31(+). Paint applies an inverse alpha of Paint object on the shadow layer
880+ // when collapsing mode is scale and shadow color is opaque. The workaround is to set the shadow
881+ // not opaque. Then Paint will respect to the color's alpha. Applying the shadow color for
882+ // expanded text.
883+ if (VERSION .SDK_INT >= VERSION_CODES .S ) {
884+ textPaint .setShadowLayer (
885+ currentShadowRadius ,
886+ currentShadowDx ,
887+ currentShadowDy ,
888+ MaterialColors .compositeARGBWithAlpha (currentShadowColor , textPaint .getAlpha ()));
889+ }
873890 textLayout .draw (canvas );
874891
875892 // Collapsed text
876893 textPaint .setAlpha ((int ) (collapsedTextBlend * originalAlpha ));
894+ // Workaround for API 31(+). Applying the shadow color for collapsed texct.
895+ if (VERSION .SDK_INT >= VERSION_CODES .S ) {
896+ textPaint .setShadowLayer (
897+ currentShadowRadius ,
898+ currentShadowDx ,
899+ currentShadowDy ,
900+ MaterialColors .compositeARGBWithAlpha (currentShadowColor , textPaint .getAlpha ()));
901+ }
877902 int lineBaseline = textLayout .getLineBaseline (0 );
878903 canvas .drawText (
879904 textToDrawCollapsed ,
@@ -882,6 +907,13 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
882907 /* x = */ 0 ,
883908 lineBaseline ,
884909 textPaint );
910+ // Reverse workaround for API 31(+). Applying opaque shadow color after the expanded text and
911+ // the collapsed text are drawn.
912+ if (VERSION .SDK_INT >= VERSION_CODES .S ) {
913+ textPaint .setShadowLayer (
914+ currentShadowRadius , currentShadowDx , currentShadowDy , currentShadowColor );
915+ }
916+
885917 if (!fadeModeEnabled ) {
886918 // Remove ellipsis for Cross-section animation
887919 String tmp = textToDrawCollapsed .toString ().trim ();
@@ -991,9 +1023,10 @@ private void calculateUsingTextSize(final float fraction, boolean forceRecalcula
9911023 // the collapsed width
9921024 // Otherwise we'll just use the expanded width
9931025
994- availableWidth = scaledDownWidth > collapsedWidth
995- ? min (collapsedWidth / textSizeRatio , expandedWidth )
996- : expandedWidth ;
1026+ availableWidth =
1027+ scaledDownWidth > collapsedWidth
1028+ ? min (collapsedWidth / textSizeRatio , expandedWidth )
1029+ : expandedWidth ;
9971030 }
9981031 }
9991032
@@ -1197,18 +1230,27 @@ public ColorStateList getCollapsedTextColor() {
11971230 }
11981231
11991232 /**
1200- * Blend {@code color1} and {@code color2} using the given ratio.
1233+ * Blend between two ARGB colors using the given ratio.
1234+ *
1235+ * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend, 1.0 will
1236+ * result in {@code color2}.
12011237 *
1202- * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
1203- * 1.0 will return {@code color2}.
1238+ * <p>This is different from the AndroidX implementation by rounding the blended channel values
1239+ * with {@link Math#round(float)}.
1240+ *
1241+ * @param color1 the first ARGB color
1242+ * @param color2 the second ARGB color
1243+ * @param ratio the blend ratio of {@code color1} to {@code color2}
12041244 */
1205- private static int blendColors (int color1 , int color2 , float ratio ) {
1206- final float inverseRatio = 1f - ratio ;
1207- float a = (Color .alpha (color1 ) * inverseRatio ) + (Color .alpha (color2 ) * ratio );
1208- float r = (Color .red (color1 ) * inverseRatio ) + (Color .red (color2 ) * ratio );
1209- float g = (Color .green (color1 ) * inverseRatio ) + (Color .green (color2 ) * ratio );
1210- float b = (Color .blue (color1 ) * inverseRatio ) + (Color .blue (color2 ) * ratio );
1211- return Color .argb ((int ) a , (int ) r , (int ) g , (int ) b );
1245+ @ ColorInt
1246+ private static int blendARGB (
1247+ @ ColorInt int color1 , @ ColorInt int color2 , @ FloatRange (from = 0.0 , to = 1.0 ) float ratio ) {
1248+ final float inverseRatio = 1 - ratio ;
1249+ float a = Color .alpha (color1 ) * inverseRatio + Color .alpha (color2 ) * ratio ;
1250+ float r = Color .red (color1 ) * inverseRatio + Color .red (color2 ) * ratio ;
1251+ float g = Color .green (color1 ) * inverseRatio + Color .green (color2 ) * ratio ;
1252+ float b = Color .blue (color1 ) * inverseRatio + Color .blue (color2 ) * ratio ;
1253+ return Color .argb (Math .round (a ), Math .round (r ), Math .round (g ), Math .round (b ));
12121254 }
12131255
12141256 private static float lerp (
0 commit comments