Skip to content

Commit

Permalink
[TextField] Fix cutout not working when backgroundTint is set
Browse files Browse the repository at this point in the history
When backgroundTint is set against the AutoCompleteTextView, since the background drawable of it is a LayerDrawable, which will try to get the constant states of its child drawables and create new drawables from them.

In CutoutDrawable we didn't implement that logic - so the constant state returned will be MaterialShapeDrawableState, and therefore the new drawables created are plain MaterialShapeDrawables, instead of CutoutDrawable.

Fixes this by correctly implement drawable state for CutoutDrawable.

Resolves #3041

PiperOrigin-RevId: 508664043
  • Loading branch information
drchen authored and dsn5ft committed Feb 10, 2023
1 parent 4b1a890 commit 25b3c2b
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ public MaterialShapeDrawable(@NonNull ShapeAppearanceModel shapeAppearanceModel)
this(new MaterialShapeDrawableState(shapeAppearanceModel, null));
}

private MaterialShapeDrawable(@NonNull MaterialShapeDrawableState drawableState) {
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
protected MaterialShapeDrawable(@NonNull MaterialShapeDrawableState drawableState) {
this.drawableState = drawableState;
strokePaint.setStyle(Style.STROKE);
fillPaint.setStyle(Style.FILL);
Expand Down Expand Up @@ -1394,39 +1398,45 @@ public boolean isRoundRect() {
return drawableState.shapeAppearanceModel.isRoundRect(getBoundsAsRectF());
}

static final class MaterialShapeDrawableState extends ConstantState {
/**
* Drawable state for {@link MaterialShapeDrawable}
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
protected static class MaterialShapeDrawableState extends ConstantState {

@NonNull public ShapeAppearanceModel shapeAppearanceModel;
@Nullable public ElevationOverlayProvider elevationOverlayProvider;
@NonNull ShapeAppearanceModel shapeAppearanceModel;
@Nullable ElevationOverlayProvider elevationOverlayProvider;

@Nullable public ColorFilter colorFilter;
@Nullable public ColorStateList fillColor = null;
@Nullable public ColorStateList strokeColor = null;
@Nullable public ColorStateList strokeTintList = null;
@Nullable public ColorStateList tintList = null;
@Nullable public PorterDuff.Mode tintMode = PorterDuff.Mode.SRC_IN;
@Nullable public Rect padding = null;
@Nullable ColorFilter colorFilter;
@Nullable ColorStateList fillColor = null;
@Nullable ColorStateList strokeColor = null;
@Nullable ColorStateList strokeTintList = null;
@Nullable ColorStateList tintList = null;
@Nullable PorterDuff.Mode tintMode = PorterDuff.Mode.SRC_IN;
@Nullable Rect padding = null;

public float scale = 1f;
public float interpolation = 1f;
public float strokeWidth;
float scale = 1f;
float interpolation = 1f;
float strokeWidth;

public int alpha = 255;
public float parentAbsoluteElevation = 0;
public float elevation = 0;
public float translationZ = 0;
public int shadowCompatMode = SHADOW_COMPAT_MODE_DEFAULT;
public int shadowCompatRadius = 0;
public int shadowCompatOffset = 0;
public int shadowCompatRotation = 0;
int alpha = 255;
float parentAbsoluteElevation = 0;
float elevation = 0;
float translationZ = 0;
int shadowCompatMode = SHADOW_COMPAT_MODE_DEFAULT;
int shadowCompatRadius = 0;
int shadowCompatOffset = 0;
int shadowCompatRotation = 0;

public boolean useTintColorForShadow = false;
boolean useTintColorForShadow = false;

public Style paintStyle = Style.FILL_AND_STROKE;
Style paintStyle = Style.FILL_AND_STROKE;

public MaterialShapeDrawableState(
ShapeAppearanceModel shapeAppearanceModel,
ElevationOverlayProvider elevationOverlayProvider) {
@NonNull ShapeAppearanceModel shapeAppearanceModel,
@Nullable ElevationOverlayProvider elevationOverlayProvider) {
this.shapeAppearanceModel = shapeAppearanceModel;
this.elevationOverlayProvider = elevationOverlayProvider;
}
Expand Down
78 changes: 58 additions & 20 deletions lib/java/com/google/android/material/textfield/CutoutDrawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
Expand All @@ -38,31 +39,44 @@
* outline mode.
*/
class CutoutDrawable extends MaterialShapeDrawable {
@NonNull protected final RectF cutoutBounds;
@NonNull CutoutDrawableState drawableState;

static CutoutDrawable create(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
return create(new CutoutDrawableState(
shapeAppearanceModel != null ? shapeAppearanceModel : new ShapeAppearanceModel(),
new RectF()));
}

private static CutoutDrawable create(@NonNull CutoutDrawableState drawableState) {
return VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2
? new ImplApi18(shapeAppearanceModel)
: new ImplApi14(shapeAppearanceModel);
? new ImplApi18(drawableState)
: new ImplApi14(drawableState);
}

private CutoutDrawable(@NonNull CutoutDrawableState drawableState) {
super(drawableState);
this.drawableState = drawableState;
}

private CutoutDrawable(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
super(shapeAppearanceModel != null ? shapeAppearanceModel : new ShapeAppearanceModel());
cutoutBounds = new RectF();
@NonNull
@Override
public Drawable mutate() {
drawableState = new CutoutDrawableState(drawableState);
return this;
}

boolean hasCutout() {
return !cutoutBounds.isEmpty();
return !drawableState.cutoutBounds.isEmpty();
}

void setCutout(float left, float top, float right, float bottom) {
// Avoid expensive redraws by only calling invalidateSelf if one of the cutout's dimensions has
// changed.
if (left != cutoutBounds.left
|| top != cutoutBounds.top
|| right != cutoutBounds.right
|| bottom != cutoutBounds.bottom) {
cutoutBounds.set(left, top, right, bottom);
if (left != drawableState.cutoutBounds.left
|| top != drawableState.cutoutBounds.top
|| right != drawableState.cutoutBounds.right
|| bottom != drawableState.cutoutBounds.bottom) {
drawableState.cutoutBounds.set(left, top, right, bottom);
invalidateSelf();
}
}
Expand All @@ -78,21 +92,21 @@ void removeCutout() {

@TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
private static class ImplApi18 extends CutoutDrawable {
ImplApi18(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
super(shapeAppearanceModel);
ImplApi18(@NonNull CutoutDrawableState drawableState) {
super(drawableState);
}

@Override
protected void drawStrokeShape(@NonNull Canvas canvas) {
if (cutoutBounds.isEmpty()) {
if (drawableState.cutoutBounds.isEmpty()) {
super.drawStrokeShape(canvas);
} else {
// Saves the canvas so we can restore the clip after drawing the stroke.
canvas.save();
if (VERSION.SDK_INT >= VERSION_CODES.O) {
canvas.clipOutRect(cutoutBounds);
canvas.clipOutRect(drawableState.cutoutBounds);
} else {
canvas.clipRect(cutoutBounds, Op.DIFFERENCE);
canvas.clipRect(drawableState.cutoutBounds, Op.DIFFERENCE);
}
super.drawStrokeShape(canvas);
canvas.restore();
Expand All @@ -106,8 +120,8 @@ private static class ImplApi14 extends CutoutDrawable {
private Paint cutoutPaint;
private int savedLayer;

ImplApi14(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
super(shapeAppearanceModel);
ImplApi14(@NonNull CutoutDrawableState drawableState) {
super(drawableState);
}

@Override
Expand All @@ -120,7 +134,7 @@ public void draw(@NonNull Canvas canvas) {
@Override
protected void drawStrokeShape(@NonNull Canvas canvas) {
super.drawStrokeShape(canvas);
canvas.drawRect(cutoutBounds, getCutoutPaint());
canvas.drawRect(drawableState.cutoutBounds, getCutoutPaint());
}

private Paint getCutoutPaint() {
Expand Down Expand Up @@ -168,4 +182,28 @@ private boolean useHardwareLayer(Callback callback) {
return callback instanceof View;
}
}

private static final class CutoutDrawableState extends MaterialShapeDrawableState {
@NonNull private final RectF cutoutBounds;

private CutoutDrawableState(
@NonNull ShapeAppearanceModel shapeAppearanceModel, @NonNull RectF cutoutBounds) {
super(shapeAppearanceModel, null);
this.cutoutBounds = cutoutBounds;
}

private CutoutDrawableState(@NonNull CutoutDrawableState state) {
super(state);
this.cutoutBounds = state.cutoutBounds;
}

@NonNull
@Override
public Drawable newDrawable() {
CutoutDrawable drawable = CutoutDrawable.create(this);
// Force the calculation of the path for the new drawable.
drawable.invalidateSelf();
return drawable;
}
}
}

0 comments on commit 25b3c2b

Please sign in to comment.