23
23
24
24
import android .animation .TimeInterpolator ;
25
25
import android .animation .ValueAnimator ;
26
+ import android .content .Context ;
26
27
import android .content .res .ColorStateList ;
27
28
import android .content .res .TypedArray ;
28
29
import android .graphics .Color ;
47
48
import androidx .annotation .StyleRes ;
48
49
import androidx .cardview .widget .CardView ;
49
50
import androidx .core .graphics .drawable .DrawableCompat ;
51
+ import androidx .dynamicanimation .animation .SpringForce ;
50
52
import com .google .android .material .animation .AnimationUtils ;
51
53
import com .google .android .material .card .MaterialCardView .CheckedIconGravity ;
52
54
import com .google .android .material .color .MaterialColors ;
56
58
import com .google .android .material .shape .CutCornerTreatment ;
57
59
import com .google .android .material .shape .MaterialShapeDrawable ;
58
60
import com .google .android .material .shape .RoundedCornerTreatment ;
61
+ import com .google .android .material .shape .ShapeAppearance ;
59
62
import com .google .android .material .shape .ShapeAppearanceModel ;
63
+ import com .google .android .material .shape .StateListShapeAppearanceModel ;
60
64
61
65
/** @hide */
62
66
@ RestrictTo (LIBRARY_GROUP )
@@ -89,6 +93,8 @@ class MaterialCardViewHelper {
89
93
90
94
private static final int CHECKED_ICON_LAYER_INDEX = 2 ;
91
95
96
+ private static final int NOT_SET = -1 ;
97
+
92
98
// We need to create a dummy drawable to avoid LayerDrawable crashes on API 28-.
93
99
private static final Drawable CHECKED_ICON_NONE =
94
100
VERSION .SDK_INT <= VERSION_CODES .P ? new ColorDrawable () : null ;
@@ -101,6 +107,7 @@ class MaterialCardViewHelper {
101
107
102
108
// Will always wrapped in an InsetDrawable
103
109
@ NonNull private final MaterialShapeDrawable foregroundContentDrawable ;
110
+ private float cardCornerRadius = NOT_SET ;
104
111
105
112
@ Dimension private int checkedIconMargin ;
106
113
@ Dimension private int checkedIconSize ;
@@ -112,7 +119,7 @@ class MaterialCardViewHelper {
112
119
@ Nullable private Drawable checkedIcon ;
113
120
@ Nullable private ColorStateList rippleColor ;
114
121
@ Nullable private ColorStateList checkedIconTint ;
115
- @ Nullable private ShapeAppearanceModel shapeAppearanceModel ;
122
+ @ NonNull private ShapeAppearance shapeAppearanceModel ;
116
123
@ Nullable private ColorStateList strokeColor ;
117
124
@ Nullable private Drawable rippleDrawable ;
118
125
@ Nullable private LayerDrawable clickableForegroundDrawable ;
@@ -135,11 +142,6 @@ public MaterialCardViewHelper(
135
142
int defStyleAttr ,
136
143
@ StyleRes int defStyleRes ) {
137
144
materialCardView = card ;
138
- bgDrawable = new MaterialShapeDrawable (card .getContext (), attrs , defStyleAttr , defStyleRes );
139
- bgDrawable .initializeElevationOverlay (card .getContext ());
140
- bgDrawable .setShadowColor (Color .DKGRAY );
141
- ShapeAppearanceModel .Builder shapeAppearanceModelBuilder =
142
- bgDrawable .getShapeAppearanceModel ().toBuilder ();
143
145
144
146
TypedArray cardViewAttributes =
145
147
card .getContext ()
@@ -148,15 +150,21 @@ public MaterialCardViewHelper(
148
150
androidx .cardview .R .styleable .CardView ,
149
151
defStyleAttr ,
150
152
androidx .cardview .R .style .CardView );
153
+ bgDrawable = new MaterialShapeDrawable (card .getContext (), attrs , defStyleAttr , defStyleRes );
154
+ bgDrawable .initializeElevationOverlay (card .getContext ());
155
+ bgDrawable .setShadowColor (Color .DKGRAY );
156
+ ShapeAppearanceModel .Builder shapeAppearanceModelBuilder =
157
+ bgDrawable .getShapeAppearanceModel ().toBuilder ();
158
+
151
159
if (cardViewAttributes .hasValue (androidx .cardview .R .styleable .CardView_cardCornerRadius )) {
152
- // If cardCornerRadius is set, let it override the shape appearance.
153
- shapeAppearanceModelBuilder . setAllCornerSizes (
154
- cardViewAttributes . getDimension (
155
- androidx . cardview . R . styleable . CardView_cardCornerRadius , 0 ) );
160
+ // If cardCornerRadius is set, remember it so we can let it override the shape appearance
161
+ cardCornerRadius = cardViewAttributes . getDimension (
162
+ androidx . cardview . R . styleable . CardView_cardCornerRadius , 0 );
163
+ shapeAppearanceModelBuilder . setAllCornerSizes ( cardCornerRadius );
156
164
}
157
165
158
166
foregroundContentDrawable = new MaterialShapeDrawable ();
159
- setShapeAppearanceModel (shapeAppearanceModelBuilder .build ());
167
+ setShapeAppearance (shapeAppearanceModelBuilder .build ());
160
168
161
169
iconFadeAnimInterpolator =
162
170
MotionUtils .resolveThemeInterpolator (
@@ -224,6 +232,24 @@ void loadFromAttributes(@NonNull TypedArray attributes) {
224
232
fgDrawable =
225
233
shouldUseClickableForeground () ? getClickableForeground () : foregroundContentDrawable ;
226
234
materialCardView .setForeground (insetDrawable (fgDrawable ));
235
+
236
+ // Card corner radius overrides the shape appearance in precedence.
237
+ if (cardCornerRadius == NOT_SET ) {
238
+ StateListShapeAppearanceModel stateListShapeAppearanceModel =
239
+ StateListShapeAppearanceModel .create (
240
+ materialCardView .getContext (),
241
+ attributes ,
242
+ R .styleable .MaterialCardView_shapeAppearance );
243
+ if (stateListShapeAppearanceModel != null ) {
244
+ SpringForce springForce = createSpringForce (materialCardView .getContext ());
245
+ bgDrawable .setCornerSpringForce (springForce );
246
+ foregroundContentDrawable .setCornerSpringForce (springForce );
247
+ if (foregroundShapeDrawable != null ) {
248
+ foregroundShapeDrawable .setCornerSpringForce (springForce );
249
+ }
250
+ setShapeAppearance (stateListShapeAppearanceModel );
251
+ }
252
+ }
227
253
}
228
254
229
255
boolean isBackgroundOverwritten () {
@@ -333,7 +359,9 @@ public void animateCheckedIcon(boolean checked) {
333
359
}
334
360
335
361
void setCornerRadius (float cornerRadius ) {
336
- setShapeAppearanceModel (shapeAppearanceModel .withCornerSize (cornerRadius ));
362
+ cardCornerRadius = cornerRadius ;
363
+ setShapeAppearance (
364
+ shapeAppearanceModel .getDefaultShape ().withCornerSize (cornerRadius ));
337
365
fgDrawable .invalidateSelf ();
338
366
if (shouldAddCornerPaddingOutsideCardBackground ()
339
367
|| shouldAddCornerPaddingInsideCardBackground ()) {
@@ -525,20 +553,18 @@ void forceRippleRedraw() {
525
553
}
526
554
}
527
555
528
- void setShapeAppearanceModel (@ NonNull ShapeAppearanceModel shapeAppearanceModel ) {
556
+ void setShapeAppearance (@ NonNull ShapeAppearance shapeAppearanceModel ) {
529
557
this .shapeAppearanceModel = shapeAppearanceModel ;
530
- bgDrawable .setShapeAppearanceModel (shapeAppearanceModel );
531
- bgDrawable .setShadowBitmapDrawingEnable (!bgDrawable .isRoundRect ());
532
- if (foregroundContentDrawable != null ) {
533
- foregroundContentDrawable .setShapeAppearanceModel (shapeAppearanceModel );
534
- }
535
-
558
+ bgDrawable .setShapeAppearance (shapeAppearanceModel );
559
+ foregroundContentDrawable .setShapeAppearance (shapeAppearanceModel );
536
560
if (foregroundShapeDrawable != null ) {
537
- foregroundShapeDrawable .setShapeAppearanceModel (shapeAppearanceModel );
561
+ foregroundShapeDrawable .setShapeAppearance (shapeAppearanceModel );
538
562
}
563
+ bgDrawable .setShadowBitmapDrawingEnable (!bgDrawable .isRoundRect ());
539
564
}
540
565
541
- ShapeAppearanceModel getShapeAppearanceModel () {
566
+ @ NonNull
567
+ ShapeAppearance getShapeAppearance () {
542
568
return shapeAppearanceModel ;
543
569
}
544
570
@@ -639,15 +665,7 @@ && canClipToOutline()
639
665
&& materialCardView .getUseCompatPadding ();
640
666
}
641
667
642
- /**
643
- * Calculates the amount of padding required between the card background shape and the card
644
- * content such that the entire content is within the bounds of the card background shape.
645
- *
646
- * <p>This should only be called when either {@link
647
- * #shouldAddCornerPaddingOutsideCardBackground()} or {@link
648
- * #shouldAddCornerPaddingInsideCardBackground()} returns true.
649
- */
650
- private float calculateActualCornerPadding () {
668
+ private float getMaxCornerPadding (@ NonNull ShapeAppearanceModel shapeAppearanceModel ) {
651
669
return Math .max (
652
670
Math .max (
653
671
calculateCornerPaddingForCornerTreatment (
@@ -664,6 +682,26 @@ private float calculateActualCornerPadding() {
664
682
bgDrawable .getBottomLeftCornerResolvedSize ())));
665
683
}
666
684
685
+ /**
686
+ * Calculates the amount of padding required between the card background shape and the card
687
+ * content such that the entire content is within the bounds of the card background shape.
688
+ *
689
+ * <p>This should only be called when either {@link
690
+ * #shouldAddCornerPaddingOutsideCardBackground()} or {@link
691
+ * #shouldAddCornerPaddingInsideCardBackground()} returns true.
692
+ */
693
+ private float calculateActualCornerPadding () {
694
+ float maxCornerPadding = 0 ;
695
+ ShapeAppearanceModel [] shapeAppearanceModels =
696
+ shapeAppearanceModel .getShapeAppearanceModels ();
697
+ for (ShapeAppearanceModel shapeAppearanceModel : shapeAppearanceModels ) {
698
+ if (shapeAppearanceModel != null ) {
699
+ maxCornerPadding = Math .max (maxCornerPadding , getMaxCornerPadding (shapeAppearanceModel ));
700
+ }
701
+ }
702
+ return maxCornerPadding ;
703
+ }
704
+
667
705
private float calculateCornerPaddingForCornerTreatment (CornerTreatment treatment , float size ) {
668
706
if (treatment instanceof RoundedCornerTreatment ) {
669
707
return (float ) ((1 - COS_45 ) * size );
@@ -745,4 +783,12 @@ private boolean isCheckedIconEnd() {
745
783
private boolean isCheckedIconBottom () {
746
784
return (checkedIconGravity & Gravity .BOTTOM ) == Gravity .BOTTOM ;
747
785
}
786
+
787
+ @ NonNull
788
+ private SpringForce createSpringForce (@ NonNull Context context ) {
789
+ return MotionUtils .resolveThemeSpringForce (
790
+ context ,
791
+ R .attr .motionSpringFastSpatial ,
792
+ R .style .Motion_Material3_Spring_Standard_Fast_Spatial );
793
+ }
748
794
}
0 commit comments