From 250d56de4b33e8ae25aa056be519ddf507c06880 Mon Sep 17 00:00:00 2001 From: Gabriele Mariotti Date: Thu, 4 Nov 2021 16:52:16 -0400 Subject: [PATCH] [MaterialCardView] Added option to set the checkedIcon gravity Resolves https://github.com/material-components/material-components-android/pull/1679 GIT_ORIGIN_REV_ID=a35c862071d83ced72c4d7257f2b7bc93101bfbb PiperOrigin-RevId: 407662373 --- docs/components/Card.md | 15 ++-- .../material/card/MaterialCardView.java | 70 +++++++++++++++++++ .../material/card/MaterialCardViewHelper.java | 50 +++++++++++-- .../card/res-public/values/public.xml | 1 + .../material/card/res/values/attrs.xml | 11 +++ 5 files changed, 136 insertions(+), 11 deletions(-) diff --git a/docs/components/Card.md b/docs/components/Card.md index 6c28e7ed700..cc9c8603234 100644 --- a/docs/components/Card.md +++ b/docs/components/Card.md @@ -380,13 +380,14 @@ border, regardless of the `app:strokeWidth` value. #### Checked icon attributes -Element | Attribute | Related method(s) | Default value -------------- | ------------------- | ------------------------------------------------------------------------------------ | ------------- -**Icon** | `checkedIcon` | `setCheckedIcon`
`setCheckedIconResource`
`getCheckedIcon` | [`@drawable/ic_mtrl_checked_circle.xml`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/resources/res/drawable/ic_mtrl_checked_circle.xml) -**Color** | `checkedIconTint` | `setCheckedIconTint`
`getCheckedIconTint` | `?attr/colorOutline` (unchecked)
`?attr/colorSecondary` (checked) -**Checkable** | `android:checkable` | `setCheckable`
`isCheckable` | `false` -**Size** | `checkedIconSize` | `setCheckedIconSize`
`setCheckedIconSizeResource`
`getCheckedIconSize` | `24dp` -**Margin** | `checkedIconMargin` | `setCheckedIconMargin`
`setCheckedIconMarginResource`
`getCheckedIconMargin` | `8dp` +Element | Attribute | Related method(s) | Default value +------------- | -------------------- | ------------------------------------------------------------------------------------ | ------------- +**Icon** | `checkedIcon` | `setCheckedIcon`
`setCheckedIconResource`
`getCheckedIcon` | [`@drawable/ic_mtrl_checked_circle.xml`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/resources/res/drawable/ic_mtrl_checked_circle.xml) +**Color** | `checkedIconTint` | `setCheckedIconTint`
`getCheckedIconTint` | `?attr/colorOutline` (unchecked)
`?attr/colorSecondary` (checked) +**Checkable** | `android:checkable` | `setCheckable`
`isCheckable` | `false` +**Size** | `checkedIconSize` | `setCheckedIconSize`
`setCheckedIconSizeResource`
`getCheckedIconSize` | `24dp` +**Margin** | `checkedIconMargin` | `setCheckedIconMargin`
`setCheckedIconMarginResource`
`getCheckedIconMargin` | `8dp` +**Gravity** | `checkedIconGravity` | `setCheckedIconGravity`
`getCheckedIconGravity` | `TOP_END` #### States diff --git a/lib/java/com/google/android/material/card/MaterialCardView.java b/lib/java/com/google/android/material/card/MaterialCardView.java index ada421427ac..7dea7183f4c 100644 --- a/lib/java/com/google/android/material/card/MaterialCardView.java +++ b/lib/java/com/google/android/material/card/MaterialCardView.java @@ -40,6 +40,7 @@ import androidx.annotation.Dimension; import androidx.annotation.DrawableRes; import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.cardview.widget.CardView; @@ -48,6 +49,8 @@ import com.google.android.material.shape.MaterialShapeUtils; import com.google.android.material.shape.ShapeAppearanceModel; import com.google.android.material.shape.Shapeable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Provides a Material card. @@ -94,6 +97,48 @@ public interface OnCheckedChangeListener { private static final String LOG_TAG = "MaterialCardView"; private static final String ACCESSIBILITY_CLASS_NAME = "androidx.cardview.widget.CardView"; + /** + * Gravity used to position the checked icon at the top|start of the Card. + * + * @see #setCheckedIconGravity(int) + * @see #getCheckedIconGravity() + */ + public static final int CHECKED_ICON_GRAVITY_TOP_START = 0x1; + + /** + * Gravity used to position the checked icon at the bottom|start of the Card. + * + * @see #setCheckedIconGravity(int) + * @see #getCheckedIconGravity() + */ + public static final int CHECKED_ICON_GRAVITY_BOTTOM_START = 0x2; + + /** + * Gravity used to position the checked icon at the top|end of the Card. + * + * @see #setCheckedIconGravity(int) + * @see #getCheckedIconGravity() + */ + public static final int CHECKED_ICON_GRAVITY_TOP_END = 0x3; + + /** + * Gravity used to position the checked icon at the bottom|end of the Card. + * + * @see #setCheckedIconGravity(int) + * @see #getCheckedIconGravity() + */ + public static final int CHECKED_ICON_GRAVITY_BOTTOM_END = 0x4; + + /** Positions the icon can be set to. */ + @IntDef({ + CHECKED_ICON_GRAVITY_TOP_START, + CHECKED_ICON_GRAVITY_BOTTOM_START, + CHECKED_ICON_GRAVITY_TOP_END, + CHECKED_ICON_GRAVITY_BOTTOM_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CheckedIconGravity{} + @NonNull private final MaterialCardViewHelper cardViewHelper; /** @@ -635,4 +680,29 @@ private void forceRippleRedrawIfNeeded() { cardViewHelper.forceRippleRedraw(); } } + + /** + * Gets the checked icon gravity for this card + * + * @return Checked Icon gravity of the card. + * @attr ref com.google.android.material.R.styleable#MaterialCard_checkedIconGravity + * @see #setCheckedIconGravity(int) + */ + @CheckedIconGravity + public int getCheckedIconGravity() { + return cardViewHelper.getCheckedIconGravity(); + } + + /** + * Sets the checked icon gravity for this card + * + * @attr ref com.google.android.material.R.styleable#MaterialCard_checkedIconGravity + * @param checkedIconGravity checked icon gravity for this card + * @see #getCheckedIconGravity() + */ + public void setCheckedIconGravity(@CheckedIconGravity int checkedIconGravity) { + if (cardViewHelper.getCheckedIconGravity() != checkedIconGravity) { + cardViewHelper.setCheckedIconGravity(checkedIconGravity); + } + } } diff --git a/lib/java/com/google/android/material/card/MaterialCardViewHelper.java b/lib/java/com/google/android/material/card/MaterialCardViewHelper.java index d141ad6a093..cfdee2f9313 100644 --- a/lib/java/com/google/android/material/card/MaterialCardViewHelper.java +++ b/lib/java/com/google/android/material/card/MaterialCardViewHelper.java @@ -19,6 +19,9 @@ import com.google.android.material.R; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import static com.google.android.material.card.MaterialCardView.CHECKED_ICON_GRAVITY_BOTTOM_END; +import static com.google.android.material.card.MaterialCardView.CHECKED_ICON_GRAVITY_BOTTOM_START; +import static com.google.android.material.card.MaterialCardView.CHECKED_ICON_GRAVITY_TOP_END; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -45,6 +48,7 @@ import androidx.annotation.RestrictTo; import androidx.annotation.StyleRes; import androidx.cardview.widget.CardView; +import com.google.android.material.card.MaterialCardView.CheckedIconGravity; import com.google.android.material.color.MaterialColors; import com.google.android.material.resources.MaterialResources; import com.google.android.material.ripple.RippleUtils; @@ -96,6 +100,7 @@ class MaterialCardViewHelper { @Dimension private int checkedIconMargin; @Dimension private int checkedIconSize; + @CheckedIconGravity private int checkedIconGravity; @Dimension private int strokeWidth; // If card is clickable, this is the clickableForegroundDrawable otherwise it draws the stroke. @@ -161,6 +166,9 @@ void loadFromAttributes(@NonNull TypedArray attributes) { attributes.getDimensionPixelSize(R.styleable.MaterialCardView_checkedIconSize, 0)); setCheckedIconMargin( attributes.getDimensionPixelSize(R.styleable.MaterialCardView_checkedIconMargin, 0)); + checkedIconGravity = + attributes.getInteger( + R.styleable.MaterialCardView_checkedIconGravity, CHECKED_ICON_GRAVITY_TOP_END); rippleColor = MaterialResources.getColorStateList( @@ -413,15 +421,30 @@ void setCheckedIconMargin(@Dimension int checkedIconMargin) { void onMeasure(int measuredWidth, int measuredHeight) { if (clickableForegroundDrawable != null) { - int left = measuredWidth - checkedIconMargin - checkedIconSize; - int bottom = measuredHeight - checkedIconMargin - checkedIconSize; + int left = + isCheckedIconEnd() + ? measuredWidth - checkedIconMargin - checkedIconSize + : checkedIconMargin; + int bottom = + isCheckedIconBottom() + ? checkedIconMargin + : measuredHeight - checkedIconMargin - checkedIconSize; + boolean isPreLollipop = VERSION.SDK_INT < VERSION_CODES.LOLLIPOP; if (isPreLollipop || materialCardView.getUseCompatPadding()) { bottom -= (int) Math.ceil(2f * calculateVerticalBackgroundPadding()); left -= (int) Math.ceil(2f * calculateHorizontalBackgroundPadding()); } - int right = checkedIconMargin; + int right = + isCheckedIconEnd() + ? checkedIconMargin + : measuredWidth - checkedIconMargin - checkedIconSize; + int top = + isCheckedIconBottom() + ? measuredHeight - checkedIconMargin - checkedIconSize + : checkedIconMargin; + if (ViewCompat.getLayoutDirection(materialCardView) == ViewCompat.LAYOUT_DIRECTION_RTL) { // swap left and right int tmp = right; @@ -430,7 +453,7 @@ void onMeasure(int measuredWidth, int measuredHeight) { } clickableForegroundDrawable.setLayerInset( - CHECKED_ICON_LAYER_INDEX, left, checkedIconMargin /* top */, right, bottom); + CHECKED_ICON_LAYER_INDEX, left, top /* top */, right, bottom); } } @@ -656,4 +679,23 @@ public void setChecked(boolean checked) { checkedIcon.setAlpha(checked ? 255 : 0); } } + + @CheckedIconGravity + int getCheckedIconGravity() { + return checkedIconGravity; + } + + void setCheckedIconGravity(@CheckedIconGravity int checkedIconGravity) { + this.checkedIconGravity = checkedIconGravity; + } + + private boolean isCheckedIconEnd() { + return checkedIconGravity == CHECKED_ICON_GRAVITY_TOP_END + || checkedIconGravity == CHECKED_ICON_GRAVITY_BOTTOM_END; + } + + private boolean isCheckedIconBottom() { + return checkedIconGravity == CHECKED_ICON_GRAVITY_BOTTOM_START + || checkedIconGravity == CHECKED_ICON_GRAVITY_BOTTOM_END; + } } diff --git a/lib/java/com/google/android/material/card/res-public/values/public.xml b/lib/java/com/google/android/material/card/res-public/values/public.xml index e3ee6679798..b40f8a9545b 100644 --- a/lib/java/com/google/android/material/card/res-public/values/public.xml +++ b/lib/java/com/google/android/material/card/res-public/values/public.xml @@ -18,6 +18,7 @@ + diff --git a/lib/java/com/google/android/material/card/res/values/attrs.xml b/lib/java/com/google/android/material/card/res/values/attrs.xml index 51c9aba4ce5..cb120cdf380 100644 --- a/lib/java/com/google/android/material/card/res/values/attrs.xml +++ b/lib/java/com/google/android/material/card/res/values/attrs.xml @@ -45,6 +45,17 @@ + + + + + + + + + + +