Skip to content

Commit 25b3c2b

Browse files
drchendsn5ft
authored andcommitted
[TextField] Fix cutout not working when backgroundTint is set
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
1 parent 4b1a890 commit 25b3c2b

File tree

2 files changed

+94
-46
lines changed

2 files changed

+94
-46
lines changed

lib/java/com/google/android/material/shape/MaterialShapeDrawable.java

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,11 @@ public MaterialShapeDrawable(@NonNull ShapeAppearanceModel shapeAppearanceModel)
208208
this(new MaterialShapeDrawableState(shapeAppearanceModel, null));
209209
}
210210

211-
private MaterialShapeDrawable(@NonNull MaterialShapeDrawableState drawableState) {
211+
/**
212+
* @hide
213+
*/
214+
@RestrictTo(LIBRARY_GROUP)
215+
protected MaterialShapeDrawable(@NonNull MaterialShapeDrawableState drawableState) {
212216
this.drawableState = drawableState;
213217
strokePaint.setStyle(Style.STROKE);
214218
fillPaint.setStyle(Style.FILL);
@@ -1394,39 +1398,45 @@ public boolean isRoundRect() {
13941398
return drawableState.shapeAppearanceModel.isRoundRect(getBoundsAsRectF());
13951399
}
13961400

1397-
static final class MaterialShapeDrawableState extends ConstantState {
1401+
/**
1402+
* Drawable state for {@link MaterialShapeDrawable}
1403+
*
1404+
* @hide
1405+
*/
1406+
@RestrictTo(LIBRARY_GROUP)
1407+
protected static class MaterialShapeDrawableState extends ConstantState {
13981408

1399-
@NonNull public ShapeAppearanceModel shapeAppearanceModel;
1400-
@Nullable public ElevationOverlayProvider elevationOverlayProvider;
1409+
@NonNull ShapeAppearanceModel shapeAppearanceModel;
1410+
@Nullable ElevationOverlayProvider elevationOverlayProvider;
14011411

1402-
@Nullable public ColorFilter colorFilter;
1403-
@Nullable public ColorStateList fillColor = null;
1404-
@Nullable public ColorStateList strokeColor = null;
1405-
@Nullable public ColorStateList strokeTintList = null;
1406-
@Nullable public ColorStateList tintList = null;
1407-
@Nullable public PorterDuff.Mode tintMode = PorterDuff.Mode.SRC_IN;
1408-
@Nullable public Rect padding = null;
1412+
@Nullable ColorFilter colorFilter;
1413+
@Nullable ColorStateList fillColor = null;
1414+
@Nullable ColorStateList strokeColor = null;
1415+
@Nullable ColorStateList strokeTintList = null;
1416+
@Nullable ColorStateList tintList = null;
1417+
@Nullable PorterDuff.Mode tintMode = PorterDuff.Mode.SRC_IN;
1418+
@Nullable Rect padding = null;
14091419

1410-
public float scale = 1f;
1411-
public float interpolation = 1f;
1412-
public float strokeWidth;
1420+
float scale = 1f;
1421+
float interpolation = 1f;
1422+
float strokeWidth;
14131423

1414-
public int alpha = 255;
1415-
public float parentAbsoluteElevation = 0;
1416-
public float elevation = 0;
1417-
public float translationZ = 0;
1418-
public int shadowCompatMode = SHADOW_COMPAT_MODE_DEFAULT;
1419-
public int shadowCompatRadius = 0;
1420-
public int shadowCompatOffset = 0;
1421-
public int shadowCompatRotation = 0;
1424+
int alpha = 255;
1425+
float parentAbsoluteElevation = 0;
1426+
float elevation = 0;
1427+
float translationZ = 0;
1428+
int shadowCompatMode = SHADOW_COMPAT_MODE_DEFAULT;
1429+
int shadowCompatRadius = 0;
1430+
int shadowCompatOffset = 0;
1431+
int shadowCompatRotation = 0;
14221432

1423-
public boolean useTintColorForShadow = false;
1433+
boolean useTintColorForShadow = false;
14241434

1425-
public Style paintStyle = Style.FILL_AND_STROKE;
1435+
Style paintStyle = Style.FILL_AND_STROKE;
14261436

14271437
public MaterialShapeDrawableState(
1428-
ShapeAppearanceModel shapeAppearanceModel,
1429-
ElevationOverlayProvider elevationOverlayProvider) {
1438+
@NonNull ShapeAppearanceModel shapeAppearanceModel,
1439+
@Nullable ElevationOverlayProvider elevationOverlayProvider) {
14301440
this.shapeAppearanceModel = shapeAppearanceModel;
14311441
this.elevationOverlayProvider = elevationOverlayProvider;
14321442
}

lib/java/com/google/android/material/textfield/CutoutDrawable.java

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import android.graphics.PorterDuffXfermode;
2626
import android.graphics.RectF;
2727
import android.graphics.Region.Op;
28+
import android.graphics.drawable.Drawable;
2829
import android.os.Build.VERSION;
2930
import android.os.Build.VERSION_CODES;
3031
import android.view.View;
@@ -38,31 +39,44 @@
3839
* outline mode.
3940
*/
4041
class CutoutDrawable extends MaterialShapeDrawable {
41-
@NonNull protected final RectF cutoutBounds;
42+
@NonNull CutoutDrawableState drawableState;
4243

4344
static CutoutDrawable create(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
45+
return create(new CutoutDrawableState(
46+
shapeAppearanceModel != null ? shapeAppearanceModel : new ShapeAppearanceModel(),
47+
new RectF()));
48+
}
49+
50+
private static CutoutDrawable create(@NonNull CutoutDrawableState drawableState) {
4451
return VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2
45-
? new ImplApi18(shapeAppearanceModel)
46-
: new ImplApi14(shapeAppearanceModel);
52+
? new ImplApi18(drawableState)
53+
: new ImplApi14(drawableState);
54+
}
55+
56+
private CutoutDrawable(@NonNull CutoutDrawableState drawableState) {
57+
super(drawableState);
58+
this.drawableState = drawableState;
4759
}
4860

49-
private CutoutDrawable(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
50-
super(shapeAppearanceModel != null ? shapeAppearanceModel : new ShapeAppearanceModel());
51-
cutoutBounds = new RectF();
61+
@NonNull
62+
@Override
63+
public Drawable mutate() {
64+
drawableState = new CutoutDrawableState(drawableState);
65+
return this;
5266
}
5367

5468
boolean hasCutout() {
55-
return !cutoutBounds.isEmpty();
69+
return !drawableState.cutoutBounds.isEmpty();
5670
}
5771

5872
void setCutout(float left, float top, float right, float bottom) {
5973
// Avoid expensive redraws by only calling invalidateSelf if one of the cutout's dimensions has
6074
// changed.
61-
if (left != cutoutBounds.left
62-
|| top != cutoutBounds.top
63-
|| right != cutoutBounds.right
64-
|| bottom != cutoutBounds.bottom) {
65-
cutoutBounds.set(left, top, right, bottom);
75+
if (left != drawableState.cutoutBounds.left
76+
|| top != drawableState.cutoutBounds.top
77+
|| right != drawableState.cutoutBounds.right
78+
|| bottom != drawableState.cutoutBounds.bottom) {
79+
drawableState.cutoutBounds.set(left, top, right, bottom);
6680
invalidateSelf();
6781
}
6882
}
@@ -78,21 +92,21 @@ void removeCutout() {
7892

7993
@TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
8094
private static class ImplApi18 extends CutoutDrawable {
81-
ImplApi18(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
82-
super(shapeAppearanceModel);
95+
ImplApi18(@NonNull CutoutDrawableState drawableState) {
96+
super(drawableState);
8397
}
8498

8599
@Override
86100
protected void drawStrokeShape(@NonNull Canvas canvas) {
87-
if (cutoutBounds.isEmpty()) {
101+
if (drawableState.cutoutBounds.isEmpty()) {
88102
super.drawStrokeShape(canvas);
89103
} else {
90104
// Saves the canvas so we can restore the clip after drawing the stroke.
91105
canvas.save();
92106
if (VERSION.SDK_INT >= VERSION_CODES.O) {
93-
canvas.clipOutRect(cutoutBounds);
107+
canvas.clipOutRect(drawableState.cutoutBounds);
94108
} else {
95-
canvas.clipRect(cutoutBounds, Op.DIFFERENCE);
109+
canvas.clipRect(drawableState.cutoutBounds, Op.DIFFERENCE);
96110
}
97111
super.drawStrokeShape(canvas);
98112
canvas.restore();
@@ -106,8 +120,8 @@ private static class ImplApi14 extends CutoutDrawable {
106120
private Paint cutoutPaint;
107121
private int savedLayer;
108122

109-
ImplApi14(@Nullable ShapeAppearanceModel shapeAppearanceModel) {
110-
super(shapeAppearanceModel);
123+
ImplApi14(@NonNull CutoutDrawableState drawableState) {
124+
super(drawableState);
111125
}
112126

113127
@Override
@@ -120,7 +134,7 @@ public void draw(@NonNull Canvas canvas) {
120134
@Override
121135
protected void drawStrokeShape(@NonNull Canvas canvas) {
122136
super.drawStrokeShape(canvas);
123-
canvas.drawRect(cutoutBounds, getCutoutPaint());
137+
canvas.drawRect(drawableState.cutoutBounds, getCutoutPaint());
124138
}
125139

126140
private Paint getCutoutPaint() {
@@ -168,4 +182,28 @@ private boolean useHardwareLayer(Callback callback) {
168182
return callback instanceof View;
169183
}
170184
}
185+
186+
private static final class CutoutDrawableState extends MaterialShapeDrawableState {
187+
@NonNull private final RectF cutoutBounds;
188+
189+
private CutoutDrawableState(
190+
@NonNull ShapeAppearanceModel shapeAppearanceModel, @NonNull RectF cutoutBounds) {
191+
super(shapeAppearanceModel, null);
192+
this.cutoutBounds = cutoutBounds;
193+
}
194+
195+
private CutoutDrawableState(@NonNull CutoutDrawableState state) {
196+
super(state);
197+
this.cutoutBounds = state.cutoutBounds;
198+
}
199+
200+
@NonNull
201+
@Override
202+
public Drawable newDrawable() {
203+
CutoutDrawable drawable = CutoutDrawable.create(this);
204+
// Force the calculation of the path for the new drawable.
205+
drawable.invalidateSelf();
206+
return drawable;
207+
}
208+
}
171209
}

0 commit comments

Comments
 (0)