2323
2424import android .animation .TimeInterpolator ;
2525import android .animation .ValueAnimator ;
26+ import android .content .Context ;
2627import android .content .res .ColorStateList ;
2728import android .content .res .TypedArray ;
2829import android .graphics .Color ;
4748import androidx .annotation .StyleRes ;
4849import androidx .cardview .widget .CardView ;
4950import androidx .core .graphics .drawable .DrawableCompat ;
51+ import androidx .dynamicanimation .animation .SpringForce ;
5052import com .google .android .material .animation .AnimationUtils ;
5153import com .google .android .material .card .MaterialCardView .CheckedIconGravity ;
5254import com .google .android .material .color .MaterialColors ;
5658import com .google .android .material .shape .CutCornerTreatment ;
5759import com .google .android .material .shape .MaterialShapeDrawable ;
5860import com .google .android .material .shape .RoundedCornerTreatment ;
61+ import com .google .android .material .shape .ShapeAppearance ;
5962import com .google .android .material .shape .ShapeAppearanceModel ;
63+ import com .google .android .material .shape .StateListShapeAppearanceModel ;
6064
6165/** @hide */
6266@ RestrictTo (LIBRARY_GROUP )
@@ -89,6 +93,8 @@ class MaterialCardViewHelper {
8993
9094 private static final int CHECKED_ICON_LAYER_INDEX = 2 ;
9195
96+ private static final int NOT_SET = -1 ;
97+
9298 // We need to create a dummy drawable to avoid LayerDrawable crashes on API 28-.
9399 private static final Drawable CHECKED_ICON_NONE =
94100 VERSION .SDK_INT <= VERSION_CODES .P ? new ColorDrawable () : null ;
@@ -101,6 +107,7 @@ class MaterialCardViewHelper {
101107
102108 // Will always wrapped in an InsetDrawable
103109 @ NonNull private final MaterialShapeDrawable foregroundContentDrawable ;
110+ private float cardCornerRadius = NOT_SET ;
104111
105112 @ Dimension private int checkedIconMargin ;
106113 @ Dimension private int checkedIconSize ;
@@ -112,7 +119,7 @@ class MaterialCardViewHelper {
112119 @ Nullable private Drawable checkedIcon ;
113120 @ Nullable private ColorStateList rippleColor ;
114121 @ Nullable private ColorStateList checkedIconTint ;
115- @ Nullable private ShapeAppearanceModel shapeAppearanceModel ;
122+ @ NonNull private ShapeAppearance shapeAppearanceModel ;
116123 @ Nullable private ColorStateList strokeColor ;
117124 @ Nullable private Drawable rippleDrawable ;
118125 @ Nullable private LayerDrawable clickableForegroundDrawable ;
@@ -135,11 +142,6 @@ public MaterialCardViewHelper(
135142 int defStyleAttr ,
136143 @ StyleRes int defStyleRes ) {
137144 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 ();
143145
144146 TypedArray cardViewAttributes =
145147 card .getContext ()
@@ -148,15 +150,21 @@ public MaterialCardViewHelper(
148150 androidx .cardview .R .styleable .CardView ,
149151 defStyleAttr ,
150152 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+
151159 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 );
156164 }
157165
158166 foregroundContentDrawable = new MaterialShapeDrawable ();
159- setShapeAppearanceModel (shapeAppearanceModelBuilder .build ());
167+ setShapeAppearance (shapeAppearanceModelBuilder .build ());
160168
161169 iconFadeAnimInterpolator =
162170 MotionUtils .resolveThemeInterpolator (
@@ -224,6 +232,24 @@ void loadFromAttributes(@NonNull TypedArray attributes) {
224232 fgDrawable =
225233 shouldUseClickableForeground () ? getClickableForeground () : foregroundContentDrawable ;
226234 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+ }
227253 }
228254
229255 boolean isBackgroundOverwritten () {
@@ -333,7 +359,9 @@ public void animateCheckedIcon(boolean checked) {
333359 }
334360
335361 void setCornerRadius (float cornerRadius ) {
336- setShapeAppearanceModel (shapeAppearanceModel .withCornerSize (cornerRadius ));
362+ cardCornerRadius = cornerRadius ;
363+ setShapeAppearance (
364+ shapeAppearanceModel .getDefaultShape ().withCornerSize (cornerRadius ));
337365 fgDrawable .invalidateSelf ();
338366 if (shouldAddCornerPaddingOutsideCardBackground ()
339367 || shouldAddCornerPaddingInsideCardBackground ()) {
@@ -525,20 +553,18 @@ void forceRippleRedraw() {
525553 }
526554 }
527555
528- void setShapeAppearanceModel (@ NonNull ShapeAppearanceModel shapeAppearanceModel ) {
556+ void setShapeAppearance (@ NonNull ShapeAppearance shapeAppearanceModel ) {
529557 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 );
536560 if (foregroundShapeDrawable != null ) {
537- foregroundShapeDrawable .setShapeAppearanceModel (shapeAppearanceModel );
561+ foregroundShapeDrawable .setShapeAppearance (shapeAppearanceModel );
538562 }
563+ bgDrawable .setShadowBitmapDrawingEnable (!bgDrawable .isRoundRect ());
539564 }
540565
541- ShapeAppearanceModel getShapeAppearanceModel () {
566+ @ NonNull
567+ ShapeAppearance getShapeAppearance () {
542568 return shapeAppearanceModel ;
543569 }
544570
@@ -639,15 +665,7 @@ && canClipToOutline()
639665 && materialCardView .getUseCompatPadding ();
640666 }
641667
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 ) {
651669 return Math .max (
652670 Math .max (
653671 calculateCornerPaddingForCornerTreatment (
@@ -664,6 +682,26 @@ private float calculateActualCornerPadding() {
664682 bgDrawable .getBottomLeftCornerResolvedSize ())));
665683 }
666684
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+
667705 private float calculateCornerPaddingForCornerTreatment (CornerTreatment treatment , float size ) {
668706 if (treatment instanceof RoundedCornerTreatment ) {
669707 return (float ) ((1 - COS_45 ) * size );
@@ -745,4 +783,12 @@ private boolean isCheckedIconEnd() {
745783 private boolean isCheckedIconBottom () {
746784 return (checkedIconGravity & Gravity .BOTTOM ) == Gravity .BOTTOM ;
747785 }
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+ }
748794}
0 commit comments