Skip to content

Commit

Permalink
[CollapsingToolbarLayout] Fixed shadow issues when transition between…
Browse files Browse the repository at this point in the history
… expanded and collapsed states.

Includes two main fixes:
1. Applied a workaround for alpha issue of shadow layer in Paint for API 31(+).
- Issue: For API 31 and plus, Paint doesn't apply the correct alpha value to the shadow layer. When the shadow color is opaque (alpha=255), the alpha of shadow should use the alpha of the Paint. But it seems the inverse alpha (255-alpha) is applied to the shadow layer.
- Solution: Added a workaround to set shadow color with a real-time alpha value, Paint still respects to the shadow color's alpha when it's not opaque.

2. Fixed rounding issue when blending shadow color for API 30(-).
- Issue: The rounding of alpha value in removed blendColors() function is casting (round down). This causes the shadow color occasionally becomes non-opaque. Then Paint stops to apply the alpha value to it.
- Solution: When blending colors, round values with Math.round() (round to nearest).

Note: Fix (1) covers fix (2). But fix (2) is still worth to keep, if issue (1) is fixed in the framework in future and workaround is removed.

Resolves: #2545
PiperOrigin-RevId: 427812326
  • Loading branch information
pekingme authored and raajkumars committed Feb 11, 2022
1 parent d5856fd commit 86e8b00
Showing 1 changed file with 84 additions and 42 deletions.
Expand Up @@ -56,6 +56,7 @@
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.internal.StaticLayoutBuilderCompat.StaticLayoutBuilderCompatException;
import com.google.android.material.resources.CancelableFontCallback;
import com.google.android.material.resources.CancelableFontCallback.ApplyFont;
Expand Down Expand Up @@ -136,6 +137,10 @@ public final class CollapsingTextHelper {

private float scale;
private float currentTextSize;
private float currentShadowRadius;
private float currentShadowDx;
private float currentShadowDy;
private int currentShadowColor;

private int[] state;

Expand Down Expand Up @@ -471,11 +476,11 @@ private boolean setCollapsedTypefaceInternal(Typeface typeface) {
}
if (collapsedTypefaceDefault != typeface) {
collapsedTypefaceDefault = typeface;
collapsedTypefaceBold = TypefaceUtils.maybeCopyWithFontWeightAdjustment(
view.getContext().getResources().getConfiguration(),
typeface);
collapsedTypeface = collapsedTypefaceBold == null
? collapsedTypefaceDefault : collapsedTypefaceBold;
collapsedTypefaceBold =
TypefaceUtils.maybeCopyWithFontWeightAdjustment(
view.getContext().getResources().getConfiguration(), typeface);
collapsedTypeface =
collapsedTypefaceBold == null ? collapsedTypefaceDefault : collapsedTypefaceBold;
return true;
}
return false;
Expand All @@ -490,11 +495,11 @@ private boolean setExpandedTypefaceInternal(Typeface typeface) {
}
if (expandedTypefaceDefault != typeface) {
expandedTypefaceDefault = typeface;
expandedTypefaceBold = TypefaceUtils.maybeCopyWithFontWeightAdjustment(
view.getContext().getResources().getConfiguration(),
typeface);
expandedTypeface = expandedTypefaceBold == null
? expandedTypefaceDefault : expandedTypefaceBold;
expandedTypefaceBold =
TypefaceUtils.maybeCopyWithFontWeightAdjustment(
view.getContext().getResources().getConfiguration(), typeface);
expandedTypeface =
expandedTypefaceBold == null ? expandedTypefaceDefault : expandedTypefaceBold;
return true;
}
return false;
Expand All @@ -511,20 +516,19 @@ public Typeface getExpandedTypeface() {
public void maybeUpdateFontWeightAdjustment(@NonNull Configuration configuration) {
if (VERSION.SDK_INT >= VERSION_CODES.S) {
if (collapsedTypefaceDefault != null) {
collapsedTypefaceBold = TypefaceUtils.maybeCopyWithFontWeightAdjustment(
configuration,
collapsedTypefaceDefault);
collapsedTypefaceBold =
TypefaceUtils.maybeCopyWithFontWeightAdjustment(
configuration, collapsedTypefaceDefault);
}
if (expandedTypefaceDefault != null) {
expandedTypefaceBold = TypefaceUtils.maybeCopyWithFontWeightAdjustment(
configuration,
expandedTypefaceDefault);
expandedTypefaceBold =
TypefaceUtils.maybeCopyWithFontWeightAdjustment(configuration, expandedTypefaceDefault);
}
collapsedTypeface = collapsedTypefaceBold != null
? collapsedTypefaceBold : collapsedTypefaceDefault;
expandedTypeface = expandedTypefaceBold != null
? expandedTypefaceBold : expandedTypefaceDefault;
recalculate(/* forceRecalculate= */true);
collapsedTypeface =
collapsedTypefaceBold != null ? collapsedTypefaceBold : collapsedTypefaceDefault;
expandedTypeface =
expandedTypefaceBold != null ? expandedTypefaceBold : expandedTypefaceDefault;
recalculate(/* forceRecalculate= */ true);
}
}

Expand Down Expand Up @@ -621,7 +625,7 @@ private void calculateOffsets(final float fraction) {
// If the collapsed and expanded text colors are different, blend them based on the
// fraction
textPaint.setColor(
blendColors(
blendARGB(
getCurrentExpandedTextColor(), getCurrentCollapsedTextColor(), textBlendFraction));
} else {
textPaint.setColor(getCurrentCollapsedTextColor());
Expand All @@ -640,12 +644,15 @@ private void calculateOffsets(final float fraction) {
}
}

// Calculates paint parameters for shadow layer.
currentShadowRadius = lerp(expandedShadowRadius, collapsedShadowRadius, fraction, null);
currentShadowDx = lerp(expandedShadowDx, collapsedShadowDx, fraction, null);
currentShadowDy = lerp(expandedShadowDy, collapsedShadowDy, fraction, null);
currentShadowColor =
blendARGB(
getCurrentColor(expandedShadowColor), getCurrentColor(collapsedShadowColor), fraction);
textPaint.setShadowLayer(
lerp(expandedShadowRadius, collapsedShadowRadius, fraction, null),
lerp(expandedShadowDx, collapsedShadowDx, fraction, null),
lerp(expandedShadowDy, collapsedShadowDy, fraction, null),
blendColors(
getCurrentColor(expandedShadowColor), getCurrentColor(collapsedShadowColor), fraction));
currentShadowRadius, currentShadowDx, currentShadowDy, currentShadowColor);

if (fadeModeEnabled) {
int originalAlpha = textPaint.getAlpha();
Expand All @@ -659,8 +666,7 @@ private void calculateOffsets(final float fraction) {
ViewCompat.postInvalidateOnAnimation(view);
}

private float calculateFadeModeTextAlpha(
@FloatRange(from = 0.0, to = 1.0) float fraction) {
private float calculateFadeModeTextAlpha(@FloatRange(from = 0.0, to = 1.0) float fraction) {
if (fraction <= fadeModeThresholdFraction) {
return AnimationUtils.lerp(
/* startValue= */ 1,
Expand Down Expand Up @@ -870,10 +876,29 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
canvas.translate(currentExpandedX, y);
// Expanded text
textPaint.setAlpha((int) (expandedTextBlend * originalAlpha));
// Workaround for API 31(+). Paint applies an inverse alpha of Paint object on the shadow layer
// when collapsing mode is scale and shadow color is opaque. The workaround is to set the shadow
// not opaque. Then Paint will respect to the color's alpha. Applying the shadow color for
// expanded text.
if (VERSION.SDK_INT >= VERSION_CODES.S) {
textPaint.setShadowLayer(
currentShadowRadius,
currentShadowDx,
currentShadowDy,
MaterialColors.compositeARGBWithAlpha(currentShadowColor, textPaint.getAlpha()));
}
textLayout.draw(canvas);

// Collapsed text
textPaint.setAlpha((int) (collapsedTextBlend * originalAlpha));
// Workaround for API 31(+). Applying the shadow color for collapsed texct.
if (VERSION.SDK_INT >= VERSION_CODES.S) {
textPaint.setShadowLayer(
currentShadowRadius,
currentShadowDx,
currentShadowDy,
MaterialColors.compositeARGBWithAlpha(currentShadowColor, textPaint.getAlpha()));
}
int lineBaseline = textLayout.getLineBaseline(0);
canvas.drawText(
textToDrawCollapsed,
Expand All @@ -882,6 +907,13 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
/* x = */ 0,
lineBaseline,
textPaint);
// Reverse workaround for API 31(+). Applying opaque shadow color after the expanded text and
// the collapsed text are drawn.
if (VERSION.SDK_INT >= VERSION_CODES.S) {
textPaint.setShadowLayer(
currentShadowRadius, currentShadowDx, currentShadowDy, currentShadowColor);
}

if (!fadeModeEnabled) {
// Remove ellipsis for Cross-section animation
String tmp = textToDrawCollapsed.toString().trim();
Expand Down Expand Up @@ -991,9 +1023,10 @@ private void calculateUsingTextSize(final float fraction, boolean forceRecalcula
// the collapsed width
// Otherwise we'll just use the expanded width

availableWidth = scaledDownWidth > collapsedWidth
? min(collapsedWidth / textSizeRatio, expandedWidth)
: expandedWidth;
availableWidth =
scaledDownWidth > collapsedWidth
? min(collapsedWidth / textSizeRatio, expandedWidth)
: expandedWidth;
}
}

Expand Down Expand Up @@ -1197,18 +1230,27 @@ public ColorStateList getCollapsedTextColor() {
}

/**
* Blend {@code color1} and {@code color2} using the given ratio.
* Blend between two ARGB colors using the given ratio.
*
* <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend, 1.0 will
* result in {@code color2}.
*
* @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
* 1.0 will return {@code color2}.
* <p>This is different from the AndroidX implementation by rounding the blended channel values
* with {@link Math#round(float)}.
*
* @param color1 the first ARGB color
* @param color2 the second ARGB color
* @param ratio the blend ratio of {@code color1} to {@code color2}
*/
private static int blendColors(int color1, int color2, float ratio) {
final float inverseRatio = 1f - ratio;
float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
return Color.argb((int) a, (int) r, (int) g, (int) b);
@ColorInt
private static int blendARGB(
@ColorInt int color1, @ColorInt int color2, @FloatRange(from = 0.0, to = 1.0) float ratio) {
final float inverseRatio = 1 - ratio;
float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
return Color.argb(Math.round(a), Math.round(r), Math.round(g), Math.round(b));
}

private static float lerp(
Expand Down

2 comments on commit 86e8b00

@bazinac
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will that get into some release? Thx.

@hunterstich
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bazinac,

We're planning on cutting a 1.6.0-alpha03 release in the next day or so. It will be included in that release.

Please sign in to comment.