Skip to content

Commit 4db1900

Browse files
imhappidsn5ft
authored andcommitted
[MaterialShapeDrawable] Introduce ShapeAppearance interface to reduce branching between ShapeAppearanceModel and StateListShapeAppearanceModel
PiperOrigin-RevId: 784395554
1 parent 24cddab commit 4db1900

File tree

8 files changed

+228
-124
lines changed

8 files changed

+228
-124
lines changed

lib/java/com/google/android/material/button/MaterialButton.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import com.google.android.material.resources.MaterialResources;
8181
import com.google.android.material.shape.MaterialShapeDrawable;
8282
import com.google.android.material.shape.MaterialShapeUtils;
83+
import com.google.android.material.shape.ShapeAppearance;
8384
import com.google.android.material.shape.ShapeAppearanceModel;
8485
import com.google.android.material.shape.Shapeable;
8586
import com.google.android.material.shape.StateListShapeAppearanceModel;
@@ -298,23 +299,23 @@ public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs, in
298299
StateListShapeAppearanceModel stateListShapeAppearanceModel =
299300
StateListShapeAppearanceModel.create(
300301
context, attributes, R.styleable.MaterialButton_shapeAppearance);
301-
ShapeAppearanceModel shapeAppearanceModel =
302+
ShapeAppearance shapeAppearance =
302303
stateListShapeAppearanceModel != null
303-
? stateListShapeAppearanceModel.getDefaultShape(/* withCornerSizeOverrides= */ true)
304+
? stateListShapeAppearanceModel
304305
: ShapeAppearanceModel.builder(context, attrs, defStyleAttr, DEF_STYLE_RES).build();
305306
boolean opticalCenterEnabled =
306307
attributes.getBoolean(R.styleable.MaterialButton_opticalCenterEnabled, false);
307308

308309
// Loads and sets background drawable attributes
309-
materialButtonHelper = new MaterialButtonHelper(this, shapeAppearanceModel);
310+
materialButtonHelper = new MaterialButtonHelper(this, shapeAppearance);
310311
materialButtonHelper.loadFromAttributes(attributes);
311312

312313
// Sets the checked state after the MaterialButtonHelper is initialized.
313314
setCheckedInternal(attributes.getBoolean(R.styleable.MaterialButton_android_checked, false));
314315

315-
if (stateListShapeAppearanceModel != null) {
316+
if (shapeAppearance instanceof StateListShapeAppearanceModel) {
316317
materialButtonHelper.setCornerSpringForce(createSpringForce());
317-
materialButtonHelper.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
318+
materialButtonHelper.setShapeAppearance(shapeAppearance);
318319
}
319320
setOpticalCenterEnabled(opticalCenterEnabled);
320321

@@ -1396,7 +1397,7 @@ public void setCheckable(boolean checkable) {
13961397
@Override
13971398
public void setShapeAppearanceModel(@NonNull ShapeAppearanceModel shapeAppearanceModel) {
13981399
if (isUsingOriginalBackground()) {
1399-
materialButtonHelper.setShapeAppearanceModel(shapeAppearanceModel);
1400+
materialButtonHelper.setShapeAppearance(shapeAppearanceModel);
14001401
} else {
14011402
throw new IllegalStateException(
14021403
"Attempted to set ShapeAppearanceModel on a MaterialButton which has an overwritten"
@@ -1425,45 +1426,44 @@ public ShapeAppearanceModel getShapeAppearanceModel() {
14251426
}
14261427

14271428
/**
1428-
* Sets the {@link StateListShapeAppearanceModel} used for this {@link MaterialButton}'s original
1429+
* Sets the {@link ShapeAppearance} used for this {@link MaterialButton}'s original
14291430
* drawables.
14301431
*
14311432
* @throws IllegalStateException if the MaterialButton's background has been overwritten.
14321433
* @hide
14331434
*/
14341435
@RestrictTo(LIBRARY_GROUP)
1435-
public void setStateListShapeAppearanceModel(
1436-
@NonNull StateListShapeAppearanceModel stateListShapeAppearanceModel) {
1436+
public void setShapeAppearance(
1437+
@NonNull ShapeAppearance shapeAppearance) {
14371438
if (isUsingOriginalBackground()) {
1438-
if (materialButtonHelper.getCornerSpringForce() == null
1439-
&& stateListShapeAppearanceModel.isStateful()) {
1439+
if (materialButtonHelper.getCornerSpringForce() == null && shapeAppearance.isStateful()) {
14401440
materialButtonHelper.setCornerSpringForce(createSpringForce());
14411441
}
1442-
materialButtonHelper.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
1442+
materialButtonHelper.setShapeAppearance(shapeAppearance);
14431443
} else {
14441444
throw new IllegalStateException(
1445-
"Attempted to set StateListShapeAppearanceModel on a MaterialButton which has an"
1445+
"Attempted to set ShapeAppearance on a MaterialButton which has an"
14461446
+ " overwritten background.");
14471447
}
14481448
}
14491449

14501450
/**
1451-
* Returns the {@link StateListShapeAppearanceModel} used for this {@link MaterialButton}'s
1451+
* Returns the {@link ShapeAppearance} used for this {@link MaterialButton}'s
14521452
* original drawables.
14531453
*
1454-
* <p>This {@link StateListShapeAppearanceModel} can be modified to change the component's shape.
1454+
* <p>This {@link ShapeAppearance} can be modified to change the component's shape.
14551455
*
14561456
* @throws IllegalStateException if the MaterialButton's background has been overwritten.
14571457
* @hide
14581458
*/
1459-
@Nullable
1459+
@NonNull
14601460
@RestrictTo(LIBRARY_GROUP)
1461-
public StateListShapeAppearanceModel getStateListShapeAppearanceModel() {
1461+
public ShapeAppearance getShapeAppearance() {
14621462
if (isUsingOriginalBackground()) {
1463-
return materialButtonHelper.getStateListShapeAppearanceModel();
1463+
return materialButtonHelper.getShapeAppearance();
14641464
} else {
14651465
throw new IllegalStateException(
1466-
"Attempted to get StateListShapeAppearanceModel from a MaterialButton which has an"
1466+
"Attempted to get ShapeAppearance from a MaterialButton which has an"
14671467
+ " overwritten background.");
14681468
}
14691469
}

lib/java/com/google/android/material/button/MaterialButtonGroup.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.google.android.material.resources.MaterialAttributes;
5757
import com.google.android.material.shape.AbsoluteCornerSize;
5858
import com.google.android.material.shape.CornerSize;
59+
import com.google.android.material.shape.ShapeAppearance;
5960
import com.google.android.material.shape.ShapeAppearanceModel;
6061
import com.google.android.material.shape.StateListCornerSize;
6162
import com.google.android.material.shape.StateListShapeAppearanceModel;
@@ -146,9 +147,7 @@ public class MaterialButtonGroup extends LinearLayout {
146147

147148
private int overflowMode = OVERFLOW_MODE_NONE;
148149

149-
private final List<ShapeAppearanceModel> originalChildShapeAppearanceModels = new ArrayList<>();
150-
private final List<StateListShapeAppearanceModel> originalChildStateListShapeAppearanceModels =
151-
new ArrayList<>();
150+
private final List<ShapeAppearance> originalChildShapeAppearanceModels = new ArrayList<>();
152151

153152
private final PressedStateTracker pressedStateTracker = new PressedStateTracker();
154153
private final Comparator<MaterialButton> childOrderComparator =
@@ -319,8 +318,7 @@ public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutPa
319318
buttonChild.setOnPressedChangeListenerInternal(pressedStateTracker);
320319

321320
// Saves original child shape appearance.
322-
originalChildShapeAppearanceModels.add(buttonChild.getShapeAppearanceModel());
323-
originalChildStateListShapeAppearanceModels.add(buttonChild.getStateListShapeAppearanceModel());
321+
originalChildShapeAppearanceModels.add(buttonChild.getShapeAppearance());
324322

325323
// Enable children based on the MaterialButtonToggleGroup own isEnabled
326324
buttonChild.setEnabled(isEnabled());
@@ -337,7 +335,6 @@ public void onViewRemoved(View child) {
337335
int indexOfChild = indexOfChild(child);
338336
if (indexOfChild >= 0) {
339337
originalChildShapeAppearanceModels.remove(indexOfChild);
340-
originalChildStateListShapeAppearanceModels.remove(indexOfChild);
341338
}
342339

343340
childShapesDirty = true;
@@ -348,6 +345,15 @@ public void onViewRemoved(View child) {
348345
adjustChildMarginsAndUpdateLayout();
349346
}
350347

348+
/**
349+
* Returns the original {@link ShapeAppearanceModel} for the {@link MaterialButton} child at the
350+
* given index.
351+
*/
352+
@NonNull
353+
public ShapeAppearanceModel getChildOriginalShapeAppearanceModel(int index) {
354+
return originalChildShapeAppearanceModels.get(index).getDefaultShape();
355+
}
356+
351357
private void recoverAllChildrenLayoutParams() {
352358
for (int i = 0; i < getChildCount(); i++) {
353359
MaterialButton child = getChildButton(i);
@@ -576,12 +582,9 @@ void updateChildShapes() {
576582
originalStateListShapeBuilder
577583
.setCornerSizeOverride(innerCornerSize, cornerPositionBitsToOverride)
578584
.build();
579-
if (newStateListShape.isStateful()) {
580-
button.setStateListShapeAppearanceModel(newStateListShape);
581-
} else {
582-
button.setShapeAppearanceModel(
583-
newStateListShape.getDefaultShape(/* withCornerSizeOverrides= */ true));
584-
}
585+
button.setShapeAppearance(newStateListShape.isStateful()
586+
? newStateListShape
587+
: newStateListShape.getDefaultShape(/* withCornerSizeOverrides= */ true));
585588
}
586589
}
587590

@@ -600,14 +603,15 @@ void updateChildShapes() {
600603
@NonNull
601604
private StateListShapeAppearanceModel.Builder getOriginalStateListShapeBuilder(
602605
boolean isFirstVisible, boolean isLastVisible, int index) {
603-
StateListShapeAppearanceModel originalStateList =
606+
ShapeAppearance originalStateList =
604607
groupStateListShapeAppearance != null && (isFirstVisible || isLastVisible)
605608
? groupStateListShapeAppearance
606-
: originalChildStateListShapeAppearanceModels.get(index);
609+
: originalChildShapeAppearanceModels.get(index);
607610
// If the state list shape is not specified, creates one from the shape appearance model.
608-
return originalStateList == null
609-
? new StateListShapeAppearanceModel.Builder(originalChildShapeAppearanceModels.get(index))
610-
: originalStateList.toBuilder();
611+
return !(originalStateList instanceof StateListShapeAppearanceModel)
612+
? new StateListShapeAppearanceModel.Builder(
613+
(ShapeAppearanceModel) originalChildShapeAppearanceModels.get(index))
614+
: ((StateListShapeAppearanceModel) originalStateList).toBuilder();
611615
}
612616

613617
/**

lib/java/com/google/android/material/button/MaterialButtonHelper.java

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.google.android.material.ripple.RippleUtils;
4343
import com.google.android.material.shape.MaterialShapeDrawable;
4444
import com.google.android.material.shape.MaterialShapeDrawable.OnCornerSizeChangeListener;
45+
import com.google.android.material.shape.ShapeAppearance;
4546
import com.google.android.material.shape.ShapeAppearanceModel;
4647
import com.google.android.material.shape.Shapeable;
4748
import com.google.android.material.shape.StateListShapeAppearanceModel;
@@ -51,8 +52,7 @@
5152
class MaterialButtonHelper {
5253

5354
private final MaterialButton materialButton;
54-
@NonNull private ShapeAppearanceModel shapeAppearanceModel;
55-
@Nullable private StateListShapeAppearanceModel stateListShapeAppearanceModel;
55+
@NonNull private ShapeAppearance shapeAppearance;
5656
@Nullable private SpringForce cornerSpringForce;
5757
@Nullable private OnCornerSizeChangeListener onCornerSizeChangeListener;
5858

@@ -77,9 +77,9 @@ class MaterialButtonHelper {
7777
private LayerDrawable rippleDrawable;
7878
private int elevation;
7979

80-
MaterialButtonHelper(MaterialButton button, @NonNull ShapeAppearanceModel shapeAppearanceModel) {
80+
MaterialButtonHelper(MaterialButton button, @NonNull ShapeAppearance shapeAppearance) {
8181
materialButton = button;
82-
this.shapeAppearanceModel = shapeAppearanceModel;
82+
this.shapeAppearance = shapeAppearance;
8383
}
8484

8585
void loadFromAttributes(@NonNull TypedArray attributes) {
@@ -93,7 +93,7 @@ void loadFromAttributes(@NonNull TypedArray attributes) {
9393
// cornerRadius should override whatever corner radius is set in shapeAppearanceModel
9494
if (attributes.hasValue(R.styleable.MaterialButton_cornerRadius)) {
9595
cornerRadius = attributes.getDimensionPixelSize(R.styleable.MaterialButton_cornerRadius, -1);
96-
setShapeAppearanceModel(shapeAppearanceModel.withCornerSize(cornerRadius));
96+
setShapeAppearance(shapeAppearance.withCornerSize(cornerRadius));
9797
cornerRadiusSet = true;
9898
}
9999

@@ -208,10 +208,7 @@ void setShouldDrawSurfaceColorStroke(boolean shouldDrawSurfaceColorStroke) {
208208
* @return Drawable representing background for this button.
209209
*/
210210
private Drawable createBackground() {
211-
MaterialShapeDrawable backgroundDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
212-
if (stateListShapeAppearanceModel != null) {
213-
backgroundDrawable.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
214-
}
211+
MaterialShapeDrawable backgroundDrawable = new MaterialShapeDrawable(shapeAppearance);
215212
if (cornerSpringForce != null) {
216213
backgroundDrawable.setCornerSpringForce(cornerSpringForce);
217214
}
@@ -226,11 +223,7 @@ private Drawable createBackground() {
226223
}
227224
backgroundDrawable.setStroke(strokeWidth, strokeColor);
228225

229-
MaterialShapeDrawable surfaceColorStrokeDrawable =
230-
new MaterialShapeDrawable(shapeAppearanceModel);
231-
if (stateListShapeAppearanceModel != null) {
232-
surfaceColorStrokeDrawable.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
233-
}
226+
MaterialShapeDrawable surfaceColorStrokeDrawable = new MaterialShapeDrawable(shapeAppearance);
234227
if (cornerSpringForce != null) {
235228
surfaceColorStrokeDrawable.setCornerSpringForce(cornerSpringForce);
236229
}
@@ -241,11 +234,7 @@ private Drawable createBackground() {
241234
? MaterialColors.getColor(materialButton, R.attr.colorSurface)
242235
: Color.TRANSPARENT);
243236

244-
maskDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
245-
if (stateListShapeAppearanceModel != null) {
246-
((MaterialShapeDrawable) maskDrawable)
247-
.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
248-
}
237+
maskDrawable = new MaterialShapeDrawable(shapeAppearance);
249238
if (cornerSpringForce != null) {
250239
((MaterialShapeDrawable) maskDrawable).setCornerSpringForce(cornerSpringForce);
251240
}
@@ -332,7 +321,7 @@ void setCornerRadius(int cornerRadius) {
332321
this.cornerRadius = cornerRadius;
333322
cornerRadiusSet = true;
334323

335-
setShapeAppearanceModel(shapeAppearanceModel.withCornerSize(cornerRadius));
324+
setShapeAppearance(shapeAppearance.withCornerSize(cornerRadius));
336325
}
337326
}
338327

@@ -402,37 +391,28 @@ private void updateButtonShape() {
402391
} else {
403392
MaterialShapeDrawable backgroundDrawable = getMaterialShapeDrawable();
404393
if (backgroundDrawable != null) {
405-
if (stateListShapeAppearanceModel != null) {
406-
backgroundDrawable.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
407-
} else {
408-
backgroundDrawable.setShapeAppearanceModel(shapeAppearanceModel);
409-
}
394+
backgroundDrawable.setShapeAppearance(shapeAppearance);
410395
if (cornerSpringForce != null) {
411396
backgroundDrawable.setCornerSpringForce(cornerSpringForce);
412397
}
413398
}
414399
MaterialShapeDrawable strokeDrawable = getSurfaceColorStrokeDrawable();
415400
if (strokeDrawable != null) {
416-
if (stateListShapeAppearanceModel != null) {
417-
strokeDrawable.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
418-
} else {
419-
strokeDrawable.setShapeAppearanceModel(shapeAppearanceModel);
420-
}
401+
strokeDrawable.setShapeAppearance(shapeAppearance);
421402
if (cornerSpringForce != null) {
422403
strokeDrawable.setCornerSpringForce(cornerSpringForce);
423404
}
424405
}
425406
Shapeable animatedShapeable = getMaskDrawable();
426407
if (animatedShapeable != null) {
427-
animatedShapeable.setShapeAppearanceModel(shapeAppearanceModel);
428408
if (animatedShapeable instanceof MaterialShapeDrawable) {
429409
MaterialShapeDrawable maskDrawable = (MaterialShapeDrawable) animatedShapeable;
430-
if (stateListShapeAppearanceModel != null) {
431-
maskDrawable.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
432-
}
410+
maskDrawable.setShapeAppearance(shapeAppearance);
433411
if (cornerSpringForce != null) {
434412
maskDrawable.setCornerSpringForce(cornerSpringForce);
435413
}
414+
} else {
415+
animatedShapeable.setShapeAppearanceModel(shapeAppearance.getDefaultShape());
436416
}
437417
}
438418
}
@@ -455,7 +435,7 @@ public Shapeable getMaskDrawable() {
455435
void setCornerSpringForce(@NonNull SpringForce springForce) {
456436
this.cornerSpringForce = springForce;
457437
// We don't want to set unused spring objects.
458-
if (stateListShapeAppearanceModel != null) {
438+
if (shapeAppearance instanceof StateListShapeAppearanceModel) {
459439
updateButtonShape();
460440
}
461441
}
@@ -465,26 +445,19 @@ SpringForce getCornerSpringForce() {
465445
return this.cornerSpringForce;
466446
}
467447

468-
void setStateListShapeAppearanceModel(
469-
@NonNull StateListShapeAppearanceModel stateListShapeAppearanceModel) {
470-
this.stateListShapeAppearanceModel = stateListShapeAppearanceModel;
448+
void setShapeAppearance(@NonNull ShapeAppearance shapeAppearanceModel) {
449+
this.shapeAppearance = shapeAppearanceModel;
471450
updateButtonShape();
472451
}
473452

474-
@Nullable
475-
StateListShapeAppearanceModel getStateListShapeAppearanceModel() {
476-
return this.stateListShapeAppearanceModel;
477-
}
478-
479-
void setShapeAppearanceModel(@NonNull ShapeAppearanceModel shapeAppearanceModel) {
480-
this.shapeAppearanceModel = shapeAppearanceModel;
481-
this.stateListShapeAppearanceModel = null;
482-
updateButtonShape();
453+
@NonNull
454+
ShapeAppearance getShapeAppearance() {
455+
return shapeAppearance;
483456
}
484457

485458
@NonNull
486459
ShapeAppearanceModel getShapeAppearanceModel() {
487-
return this.shapeAppearanceModel;
460+
return shapeAppearance.getDefaultShape();
488461
}
489462

490463
public void setInsetBottom(@Dimension int newInsetBottom) {

lib/java/com/google/android/material/ripple/RippleDrawableCompat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public ShapeAppearanceModel getShapeAppearanceModel() {
9999

100100
public void setStateListShapeAppearanceModel(
101101
@NonNull StateListShapeAppearanceModel stateListShapeAppearanceModel) {
102-
drawableState.delegate.setStateListShapeAppearanceModel(stateListShapeAppearanceModel);
102+
drawableState.delegate.setShapeAppearance(stateListShapeAppearanceModel);
103103
}
104104

105105
@Nullable

0 commit comments

Comments
 (0)