Skip to content

Commit ed6fee1

Browse files
committed
[Focus Rings] Update FocusRingDrawable to automatically construct its ShapeAppearance from a child ShapeDrawable or GradientDrawable (<shape from XML)
PiperOrigin-RevId: 901446786
1 parent aa6e952 commit ed6fee1

6 files changed

Lines changed: 100 additions & 53 deletions

File tree

catalog/java/io/material/catalog/topappbar/res/drawable-v24/preference_card_item_background_first.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@
1515
limitations under the License.
1616
-->
1717
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
18-
xmlns:app="http://schemas.android.com/apk/res-auto"
1918
android:color="?android:colorControlHighlight"
2019
android:paddingStart="32dp"
2120
android:paddingEnd="32dp">
2221
<item
2322
android:start="?android:attr/listPreferredItemPaddingStart"
2423
android:end="?android:attr/listPreferredItemPaddingEnd">
25-
<com.google.android.material.focus.FocusRingDrawable
26-
app:focusRingsShapeAppearance="@style/ShapeAppearance.Preference.First">
24+
<com.google.android.material.focus.FocusRingDrawable>
2725
<shape android:shape="rectangle">
2826
<solid
2927
android:color="?attr/colorSurfaceBright" />

catalog/java/io/material/catalog/topappbar/res/drawable-v24/preference_card_item_background_last.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@
1515
limitations under the License.
1616
-->
1717
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
18-
xmlns:app="http://schemas.android.com/apk/res-auto"
1918
android:color="?android:colorControlHighlight"
2019
android:paddingStart="32dp"
2120
android:paddingEnd="32dp">
2221
<item
2322
android:start="?android:attr/listPreferredItemPaddingStart"
2423
android:end="?android:attr/listPreferredItemPaddingEnd">
25-
<com.google.android.material.focus.FocusRingDrawable
26-
app:focusRingsShapeAppearance="@style/ShapeAppearance.Preference.Last">
24+
<com.google.android.material.focus.FocusRingDrawable>
2725
<shape android:shape="rectangle">
2826
<solid
2927
android:color="?attr/colorSurfaceBright" />

catalog/java/io/material/catalog/topappbar/res/drawable-v24/preference_card_item_background_middle.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
-->
1717

1818
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
19-
xmlns:app="http://schemas.android.com/apk/res-auto"
2019
android:color="?android:colorControlHighlight"
2120
android:paddingStart="32dp"
2221
android:paddingEnd="32dp">
2322
<item
2423
android:start="?android:attr/listPreferredItemPaddingStart"
2524
android:end="?android:attr/listPreferredItemPaddingEnd">
26-
<com.google.android.material.focus.FocusRingDrawable
27-
app:focusRingsShapeAppearance="@style/ShapeAppearance.Preference.Middle">
25+
<com.google.android.material.focus.FocusRingDrawable>
2826
<shape android:shape="rectangle">
2927
<solid
3028
android:color="?attr/colorSurfaceBright" />

catalog/java/io/material/catalog/topappbar/res/drawable-v24/preference_card_item_background_single.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@
1515
limitations under the License.
1616
-->
1717
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
18-
xmlns:app="http://schemas.android.com/apk/res-auto"
1918
android:color="?android:colorControlHighlight"
2019
android:paddingStart="32dp"
2120
android:paddingEnd="32dp">
2221
<item
2322
android:start="?android:attr/listPreferredItemPaddingStart"
2423
android:end="?android:attr/listPreferredItemPaddingEnd">
25-
<com.google.android.material.focus.FocusRingDrawable
26-
app:focusRingsShapeAppearance="@style/ShapeAppearance.Preference.Single">
24+
<com.google.android.material.focus.FocusRingDrawable>
2725
<shape android:shape="rectangle">
2826
<solid
2927
android:color="?attr/colorSurfaceBright" />

catalog/java/io/material/catalog/topappbar/res/values/styles.xml

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,4 @@
5454
<item name="android:layout">@layout/cat_topappbar_preference_card_item_layout</item>
5555
</style>
5656

57-
<style name="ShapeAppearance.Preference.First" parent="">
58-
<item name="cornerFamily">rounded</item>
59-
<item name="cornerSizeTopLeft">@dimen/cat_topappbar_preference_card_item_radius_large</item>
60-
<item name="cornerSizeTopRight">@dimen/cat_topappbar_preference_card_item_radius_large</item>
61-
<item name="cornerSizeBottomLeft">@dimen/cat_topappbar_preference_card_item_radius_small</item>
62-
<item name="cornerSizeBottomRight">@dimen/cat_topappbar_preference_card_item_radius_small</item>
63-
</style>
64-
65-
<style name="ShapeAppearance.Preference.Middle" parent="">
66-
<item name="cornerFamily">rounded</item>
67-
<item name="cornerSize">@dimen/cat_topappbar_preference_card_item_radius_small</item>
68-
</style>
69-
70-
<style name="ShapeAppearance.Preference.Last" parent="">
71-
<item name="cornerFamily">rounded</item>
72-
<item name="cornerSizeTopLeft">@dimen/cat_topappbar_preference_card_item_radius_small</item>
73-
<item name="cornerSizeTopRight">@dimen/cat_topappbar_preference_card_item_radius_small</item>
74-
<item name="cornerSizeBottomLeft">@dimen/cat_topappbar_preference_card_item_radius_large</item>
75-
<item name="cornerSizeBottomRight">@dimen/cat_topappbar_preference_card_item_radius_large</item>
76-
</style>
77-
78-
<style name="ShapeAppearance.Preference.Single" parent="">
79-
<item name="cornerFamily">rounded</item>
80-
<item name="cornerSize">@dimen/cat_topappbar_preference_card_item_radius_large</item>
81-
</style>
82-
8357
</resources>

lib/java/com/google/android/material/focus/FocusRingDrawable.java

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import com.google.android.material.R;
1919

20+
import static java.lang.Math.min;
21+
2022
import android.animation.Animator;
2123
import android.animation.AnimatorListenerAdapter;
2224
import android.animation.ObjectAnimator;
@@ -28,6 +30,7 @@
2830
import android.graphics.Canvas;
2931
import android.graphics.Color;
3032
import android.graphics.Matrix;
33+
import android.graphics.Outline;
3134
import android.graphics.Paint;
3235
import android.graphics.Paint.Style;
3336
import android.graphics.Path;
@@ -36,8 +39,10 @@
3639
import android.graphics.drawable.ColorDrawable;
3740
import android.graphics.drawable.Drawable;
3841
import android.graphics.drawable.DrawableWrapper;
42+
import android.graphics.drawable.GradientDrawable;
3943
import android.graphics.drawable.LayerDrawable;
4044
import android.graphics.drawable.RippleDrawable;
45+
import android.graphics.drawable.ShapeDrawable;
4146
import android.os.Build.VERSION;
4247
import android.os.Build.VERSION_CODES;
4348
import android.util.AttributeSet;
@@ -822,25 +827,101 @@ private float calculateInnerRadius(float outerRadius) {
822827

823828
private void calculateShapeAppearanceRoundRectOrPath() {
824829
if (state.ringShapeAppearance != null) {
825-
calculateBounds(tmpRectF);
830+
updateShapeAppearanceCornerSizeOrPath(state.ringShapeAppearance);
831+
return;
832+
}
833+
ShapeAppearance shapeAppearance = toShapeAppearance(getDrawable());
834+
if (shapeAppearance != null) {
835+
updateShapeAppearanceCornerSizeOrPath(shapeAppearance);
836+
return;
837+
}
838+
shapeAppearanceCornerSize = -1;
839+
shapeAppearancePath.reset();
840+
}
826841

827-
ShapeAppearanceModel shapeAppearanceModel =
828-
state.ringShapeAppearance.getShapeForState(FOCUSED_STATE_SET);
842+
private void updateShapeAppearanceCornerSizeOrPath(ShapeAppearance shapeAppearance) {
843+
calculateBounds(tmpRectF);
829844

830-
if (shapeAppearanceModel.isRoundRect(tmpRectF)) {
831-
float outerInset = calculateOuterInset();
832-
tmpRectF.inset(outerInset, outerInset);
833-
shapeAppearanceCornerSize =
834-
shapeAppearanceModel.getTopLeftCornerSize().getCornerSize(tmpRectF);
835-
shapeAppearancePath.reset();
836-
} else {
837-
pathProvider.calculatePath(
838-
shapeAppearanceModel, null, 1, tmpRectF, null, shapeAppearancePath);
839-
shapeAppearanceCornerSize = -1;
840-
}
845+
ShapeAppearanceModel shapeAppearanceModel = shapeAppearance.getShapeForState(FOCUSED_STATE_SET);
846+
847+
if (shapeAppearanceModel.isRoundRect(tmpRectF)) {
848+
float outerInset = calculateOuterInset();
849+
tmpRectF.inset(outerInset, outerInset);
850+
shapeAppearanceCornerSize =
851+
shapeAppearanceModel.getTopLeftCornerSize().getCornerSize(tmpRectF);
852+
shapeAppearancePath.reset();
841853
} else {
854+
pathProvider.calculatePath(
855+
shapeAppearanceModel, null, 1, tmpRectF, null, shapeAppearancePath);
842856
shapeAppearanceCornerSize = -1;
843-
shapeAppearancePath.reset();
857+
}
858+
}
859+
860+
@Nullable
861+
private ShapeAppearance toShapeAppearance(@Nullable Drawable drawable) {
862+
if (drawable instanceof ShapeDrawable) {
863+
return toShapeAppearance((ShapeDrawable) drawable);
864+
}
865+
if (drawable instanceof GradientDrawable) {
866+
return toShapeAppearance((GradientDrawable) drawable);
867+
}
868+
return null;
869+
}
870+
871+
@Nullable
872+
private ShapeAppearance toShapeAppearance(@NonNull ShapeDrawable shapeDrawable) {
873+
if (VERSION.SDK_INT < VERSION_CODES.N) {
874+
return null;
875+
}
876+
Outline outline = new Outline();
877+
shapeDrawable.getOutline(outline);
878+
if (outline.getRadius() > 0) {
879+
return ShapeAppearanceModel.builder().setAllCornerSizes(outline.getRadius()).build();
880+
}
881+
return null;
882+
}
883+
884+
@Nullable
885+
private ShapeAppearance toShapeAppearance(@NonNull GradientDrawable gradientDrawable) {
886+
if (VERSION.SDK_INT < VERSION_CODES.N) {
887+
return null;
888+
}
889+
float[] cornerRadii = getCornerRadiiOrNull(gradientDrawable);
890+
if (cornerRadii != null) {
891+
// ShapeAppearance doesn't support different x and y radius values for each corner, so just
892+
// take the min of x and y for now, which shouldn't be different in most cases.
893+
return ShapeAppearanceModel.builder()
894+
.setTopLeftCornerSize(min(cornerRadii[0], cornerRadii[1]))
895+
.setTopRightCornerSize(min(cornerRadii[2], cornerRadii[3]))
896+
.setBottomRightCornerSize(min(cornerRadii[4], cornerRadii[5]))
897+
.setBottomLeftCornerSize(min(cornerRadii[6], cornerRadii[7]))
898+
.build();
899+
}
900+
float cornerRadius = getCornerRadius(gradientDrawable);
901+
if (cornerRadius > 0) {
902+
return ShapeAppearanceModel.builder().setAllCornerSizes(cornerRadius).build();
903+
}
904+
return null;
905+
}
906+
907+
@RequiresApi(api = VERSION_CODES.N)
908+
@Nullable
909+
private float[] getCornerRadiiOrNull(GradientDrawable gradientDrawable) {
910+
// Calling getCornerRadii() before GradientDrawable has its state causes an NPE.
911+
try {
912+
return gradientDrawable.getCornerRadii();
913+
} catch (NullPointerException e) {
914+
return null;
915+
}
916+
}
917+
918+
@RequiresApi(api = VERSION_CODES.N)
919+
private float getCornerRadius(GradientDrawable gradientDrawable) {
920+
// Calling getCornerRadius() before GradientDrawable has its state causes an NPE.
921+
try {
922+
return gradientDrawable.getCornerRadius();
923+
} catch (NullPointerException e) {
924+
return -1f;
844925
}
845926
}
846927

0 commit comments

Comments
 (0)