Skip to content

Commit 59acbba

Browse files
committed
[Focus Rings] Fix theme attribute resolution for FocusRingDrawable attributes
PiperOrigin-RevId: 897028575
1 parent 1e08e4e commit 59acbba

2 files changed

Lines changed: 244 additions & 56 deletions

File tree

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

Lines changed: 242 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import androidx.annotation.NonNull;
4949
import androidx.annotation.Nullable;
5050
import androidx.annotation.RequiresApi;
51+
import androidx.annotation.StyleableRes;
5152
import com.google.android.material.resources.MaterialAttributes;
5253
import com.google.android.material.shape.MaterialShapeDrawable;
5354
import com.google.android.material.shape.ShapeAppearance;
@@ -270,58 +271,238 @@ public void inflate(
270271
} else {
271272
a = res.obtainAttributes(attrs, R.styleable.FocusRingDrawable);
272273
}
273-
updateStateFromTypedArray(a, res, /* useDefaults= */ false);
274+
updateStateFromTypedArrayWithoutThemeAttrsOrDefaults(a);
274275
a.recycle();
275276

276277
inflateChildDrawable(res, parser, attrs, theme);
277278
}
278279

279-
private void updateStateFromTypedArray(
280-
@NonNull TypedArray a, @NonNull Resources res, boolean useDefaults) {
281-
if (state.ringOuterColor == Integer.MIN_VALUE) {
282-
int defaultOuterColor = useDefaults ? Color.BLACK : Integer.MIN_VALUE;
280+
private void updateStateFromTypedArrayWithoutThemeAttrsOrDefaults(@NonNull TypedArray a) {
281+
state.ringEnabledAttr = getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsEnabled);
282+
if (state.ringEnabledAttr == Integer.MIN_VALUE
283+
&& a.hasValue(R.styleable.FocusRingDrawable_focusRingsEnabled)) {
284+
state.ringEnabled =
285+
a.getBoolean(R.styleable.FocusRingDrawable_focusRingsEnabled, state.ringEnabled);
286+
state.ringEnabledInflated = true;
287+
}
288+
289+
state.ringOuterColorAttr =
290+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsOuterStrokeColor);
291+
if (state.ringOuterColorAttr == Integer.MIN_VALUE) {
283292
state.ringOuterColor =
284-
a.getColor(R.styleable.FocusRingDrawable_focusRingsOuterStrokeColor, defaultOuterColor);
293+
a.getColor(R.styleable.FocusRingDrawable_focusRingsOuterStrokeColor, Integer.MIN_VALUE);
285294
}
286-
if (state.ringInnerColor == Integer.MIN_VALUE) {
287-
int defaultInnerColor = useDefaults ? Color.WHITE : Integer.MIN_VALUE;
295+
296+
state.ringInnerColorAttr =
297+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsInnerStrokeColor);
298+
if (state.ringInnerColorAttr == Integer.MIN_VALUE) {
288299
state.ringInnerColor =
289-
a.getColor(R.styleable.FocusRingDrawable_focusRingsInnerStrokeColor, defaultInnerColor);
290-
}
291-
if (DEBUG_COLORS) {
292-
state.ringOuterColor = Color.RED;
293-
state.ringInnerColor = Color.GREEN;
300+
a.getColor(R.styleable.FocusRingDrawable_focusRingsInnerStrokeColor, Integer.MIN_VALUE);
294301
}
295-
if (Float.isNaN(state.ringOuterStrokeWidth)) {
296-
float defaultStrokeWidth =
297-
useDefaults
298-
? res.getDimensionPixelSize(R.dimen.mtrl_focus_ring_outer_stroke_width)
299-
: Float.NaN;
302+
303+
state.ringOuterStrokeWidthAttr =
304+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsOuterStrokeWidth);
305+
if (state.ringOuterStrokeWidthAttr == Integer.MIN_VALUE) {
300306
state.ringOuterStrokeWidth =
301-
a.getDimension(
302-
R.styleable.FocusRingDrawable_focusRingsOuterStrokeWidth, defaultStrokeWidth);
303-
}
304-
if (Float.isNaN(state.ringInnerStrokeWidth)) {
305-
float defaultStrokeWidth =
306-
useDefaults
307-
? res.getDimensionPixelSize(R.dimen.mtrl_focus_ring_outer_stroke_width)
308-
: Float.NaN;
307+
a.getDimension(R.styleable.FocusRingDrawable_focusRingsOuterStrokeWidth, Float.NaN);
308+
}
309+
310+
state.ringInnerStrokeWidthAttr =
311+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth);
312+
if (state.ringInnerStrokeWidthAttr == Integer.MIN_VALUE) {
313+
state.ringInnerStrokeWidth =
314+
a.getDimension(R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth, Float.NaN);
315+
}
316+
317+
state.ringInnerStrokeWidthAttr =
318+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth);
319+
if (state.ringInnerStrokeWidthAttr == Integer.MIN_VALUE) {
309320
state.ringInnerStrokeWidth =
310-
a.getDimension(
311-
R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth, defaultStrokeWidth);
321+
a.getDimension(R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth, Float.NaN);
312322
}
313-
if (Float.isNaN(state.ringRadius)) {
323+
324+
state.ringRadiusAttr = getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsRadius);
325+
if (state.ringRadiusAttr == Integer.MIN_VALUE) {
314326
state.ringRadius = a.getDimension(R.styleable.FocusRingDrawable_focusRingsRadius, Float.NaN);
315327
}
316-
if (Float.isNaN(state.ringInset)) {
317-
float defaultInset = useDefaults ? 0f : Float.NaN;
318-
state.ringInset = a.getDimension(R.styleable.FocusRingDrawable_focusRingsInset, defaultInset);
328+
329+
state.ringInsetAttr = getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsInset);
330+
if (state.ringInsetAttr == Integer.MIN_VALUE) {
331+
state.ringInset = a.getDimension(R.styleable.FocusRingDrawable_focusRingsInset, Float.NaN);
319332
}
320-
if (Float.isNaN(state.ringInnerInset)) {
321-
float defaultInset = useDefaults ? 0f : Float.NaN;
333+
334+
state.ringInnerInsetAttr =
335+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsInnerStrokeInset);
336+
if (state.ringInnerInsetAttr == Integer.MIN_VALUE) {
322337
state.ringInnerInset =
323-
a.getDimension(R.styleable.FocusRingDrawable_focusRingsInnerStrokeInset, defaultInset);
338+
a.getDimension(R.styleable.FocusRingDrawable_focusRingsInnerStrokeInset, Float.NaN);
339+
}
340+
341+
state.ringShapeAppearanceAttr =
342+
getValueDataIfAttr(a, R.styleable.FocusRingDrawable_focusRingsShapeAppearance);
343+
state.ringShapeAppearanceResId =
344+
getResIdIfReference(a, R.styleable.FocusRingDrawable_focusRingsShapeAppearance);
345+
}
346+
347+
private void updateStateFromTypedArrayWithThemeAttrsAndDefaults(
348+
@NonNull TypedArray a, @NonNull Theme theme) {
349+
Resources res = theme.getResources();
350+
351+
if (state.ringEnabledAttr != Integer.MIN_VALUE) {
352+
TypedValue typedValue = MaterialAttributes.resolve(theme, state.ringEnabledAttr);
353+
if (typedValue != null) {
354+
state.ringEnabled = typedValue.data != 0;
355+
state.ringEnabledInflated = true;
356+
}
357+
}
358+
if (!state.ringEnabledInflated) {
359+
state.ringEnabled =
360+
MaterialAttributes.resolveBoolean(theme, R.attr.focusRingsEnabled, state.ringEnabled);
361+
}
362+
if (!state.ringEnabled) {
363+
return;
364+
}
365+
366+
state.ringOuterColor =
367+
maybeResolveColor(
368+
state.ringOuterColor,
369+
theme,
370+
state.ringOuterColorAttr,
371+
a,
372+
R.styleable.FocusRingDrawable_focusRingsOuterStrokeColor,
373+
Color.BLACK);
374+
375+
state.ringInnerColor =
376+
maybeResolveColor(
377+
state.ringInnerColor,
378+
theme,
379+
state.ringInnerColorAttr,
380+
a,
381+
R.styleable.FocusRingDrawable_focusRingsInnerStrokeColor,
382+
Color.WHITE);
383+
384+
float defaultStrokeWidth =
385+
res.getDimensionPixelSize(R.dimen.mtrl_focus_ring_outer_stroke_width);
386+
387+
state.ringOuterStrokeWidth =
388+
maybeResolveDimension(
389+
state.ringOuterStrokeWidth,
390+
theme,
391+
state.ringOuterStrokeWidthAttr,
392+
a,
393+
R.styleable.FocusRingDrawable_focusRingsOuterStrokeWidth,
394+
defaultStrokeWidth);
395+
396+
state.ringInnerStrokeWidth =
397+
maybeResolveDimension(
398+
state.ringInnerStrokeWidth,
399+
theme,
400+
state.ringInnerStrokeWidthAttr,
401+
a,
402+
R.styleable.FocusRingDrawable_focusRingsInnerStrokeWidth,
403+
defaultStrokeWidth);
404+
405+
state.ringRadius =
406+
maybeResolveDimension(
407+
state.ringRadius,
408+
theme,
409+
state.ringRadiusAttr,
410+
a,
411+
R.styleable.FocusRingDrawable_focusRingsRadius,
412+
Float.NaN);
413+
414+
state.ringInset =
415+
maybeResolveDimension(
416+
state.ringInset,
417+
theme,
418+
state.ringInsetAttr,
419+
a,
420+
R.styleable.FocusRingDrawable_focusRingsInset,
421+
0f);
422+
423+
state.ringInnerInset =
424+
maybeResolveDimension(
425+
state.ringInnerInset,
426+
theme,
427+
state.ringInnerInsetAttr,
428+
a,
429+
R.styleable.FocusRingDrawable_focusRingsInnerStrokeInset,
430+
0f);
431+
432+
if (state.ringShapeAppearanceResId != Integer.MIN_VALUE) {
433+
state.ringShapeAppearance =
434+
ShapeAppearanceModel.builder(theme, state.ringShapeAppearanceResId).build();
435+
} else {
436+
int shapeAppearanceAttr =
437+
state.ringShapeAppearanceAttr != Integer.MIN_VALUE
438+
? state.ringShapeAppearanceAttr
439+
: R.attr.focusRingsShapeAppearance;
440+
TypedValue typedValue = MaterialAttributes.resolve(theme, shapeAppearanceAttr);
441+
if (typedValue != null) {
442+
state.ringShapeAppearance =
443+
ShapeAppearanceModel.builder(theme, typedValue.resourceId).build();
444+
}
324445
}
446+
447+
if (DEBUG_COLORS) {
448+
state.ringOuterColor = Color.RED;
449+
state.ringInnerColor = Color.GREEN;
450+
}
451+
}
452+
453+
private int getValueDataIfAttr(TypedArray a, @StyleableRes int index) {
454+
if (a.getType(index) == TypedValue.TYPE_ATTRIBUTE) {
455+
TypedValue value = new TypedValue();
456+
if (a.getValue(index, value)) {
457+
return value.data;
458+
}
459+
}
460+
return Integer.MIN_VALUE;
461+
}
462+
463+
private int getResIdIfReference(TypedArray a, @StyleableRes int index) {
464+
if (a.getType(index) == TypedValue.TYPE_REFERENCE) {
465+
return a.getResourceId(index, Integer.MIN_VALUE);
466+
}
467+
return Integer.MIN_VALUE;
468+
}
469+
470+
private int maybeResolveColor(
471+
int currentValue,
472+
@NonNull Theme theme,
473+
@StyleableRes int attrIndex,
474+
@NonNull TypedArray a,
475+
@StyleableRes int regularIndex,
476+
int defaultValue) {
477+
if (currentValue != Integer.MIN_VALUE) {
478+
return currentValue;
479+
}
480+
if (attrIndex != Integer.MIN_VALUE) {
481+
TypedValue value = new TypedValue();
482+
if (theme.resolveAttribute(attrIndex, value, true)) {
483+
return value.data;
484+
}
485+
}
486+
return a.getColor(regularIndex, defaultValue);
487+
}
488+
489+
private float maybeResolveDimension(
490+
float currentValue,
491+
@NonNull Theme theme,
492+
@StyleableRes int attrIndex,
493+
@NonNull TypedArray a,
494+
@StyleableRes int regularIndex,
495+
float defaultValue) {
496+
if (!Float.isNaN(currentValue)) {
497+
return currentValue;
498+
}
499+
if (attrIndex != Float.MIN_VALUE) {
500+
TypedValue value = new TypedValue();
501+
if (theme.resolveAttribute(attrIndex, value, true)) {
502+
return value.getDimension(theme.getResources().getDisplayMetrics());
503+
}
504+
}
505+
return a.getDimension(regularIndex, defaultValue);
325506
}
326507

327508
private void inflateChildDrawable(
@@ -356,29 +537,18 @@ private void init(@NonNull Theme theme) {
356537
return;
357538
}
358539

359-
state.ringEnabled = MaterialAttributes.resolveBoolean(theme, R.attr.focusRingsEnabled, false);
360-
361-
if (!state.ringEnabled) {
362-
return;
363-
}
364-
365-
// Shape appearance is currently only supported from theme / theme overlay.
366-
TypedValue typedValue = MaterialAttributes.resolve(theme, R.attr.focusRingsShapeAppearance);
367-
if (typedValue != null) {
368-
state.ringShapeAppearance =
369-
ShapeAppearanceModel.builder(theme, typedValue.resourceId).build();
370-
}
371-
372540
TypedArray a = theme.obtainStyledAttributes(R.styleable.FocusRingDrawable);
373-
updateStateFromTypedArray(a, theme.getResources(), /* useDefaults= */ true);
541+
updateStateFromTypedArrayWithThemeAttrsAndDefaults(a, theme);
374542
a.recycle();
375543

376544
updateLocalState();
377545
}
378546

379547
private void updateLocalState() {
380548
paint.setStyle(Style.STROKE);
381-
paint.setStrokeWidth(state.ringOuterStrokeWidth);
549+
if (!Float.isNaN(state.ringOuterStrokeWidth)) {
550+
paint.setStrokeWidth(state.ringOuterStrokeWidth);
551+
}
382552
}
383553

384554
@Override
@@ -697,15 +867,25 @@ private static final class FocusRingState extends ConstantState {
697867
int mChangingConfigurations = 0;
698868

699869
private boolean ringEnabled = false;
870+
private int ringEnabledAttr = Integer.MIN_VALUE;
871+
private boolean ringEnabledInflated = false;
700872
private int ringOuterColor = Integer.MIN_VALUE;
873+
private int ringOuterColorAttr = Integer.MIN_VALUE;
701874
private int ringInnerColor = Integer.MIN_VALUE;
875+
private int ringInnerColorAttr = Integer.MIN_VALUE;
702876
private float ringOuterStrokeWidth = Float.NaN;
877+
private int ringOuterStrokeWidthAttr = Integer.MIN_VALUE;
703878
private float ringInnerStrokeWidth = Float.NaN;
879+
private int ringInnerStrokeWidthAttr = Integer.MIN_VALUE;
704880
private float ringRadius = Float.NaN;
881+
private int ringRadiusAttr = Integer.MIN_VALUE;
705882
private float ringInset = Float.NaN;
883+
private int ringInsetAttr = Integer.MIN_VALUE;
706884
private float ringInnerInset = Float.NaN;
885+
private int ringInnerInsetAttr = Integer.MIN_VALUE;
707886
@Nullable private ShapeAppearance ringShapeAppearance = null;
708-
private int ringBoundsMode = -1;
887+
private int ringShapeAppearanceResId = Integer.MIN_VALUE;
888+
private int ringShapeAppearanceAttr = Integer.MIN_VALUE;
709889
@Nullable private Rect ringCustomBounds = null;
710890

711891
FocusRingState(@Nullable FocusRingState orig) {
@@ -714,15 +894,25 @@ private static final class FocusRingState extends ConstantState {
714894
mChangingConfigurations = orig.mChangingConfigurations;
715895

716896
this.ringEnabled = orig.ringEnabled;
897+
this.ringEnabledAttr = orig.ringEnabledAttr;
898+
this.ringEnabledInflated = orig.ringEnabledInflated;
717899
this.ringOuterColor = orig.ringOuterColor;
900+
this.ringOuterColorAttr = orig.ringOuterColorAttr;
718901
this.ringInnerColor = orig.ringInnerColor;
902+
this.ringInnerColorAttr = orig.ringInnerColorAttr;
719903
this.ringOuterStrokeWidth = orig.ringOuterStrokeWidth;
904+
this.ringOuterStrokeWidthAttr = orig.ringOuterStrokeWidthAttr;
720905
this.ringInnerStrokeWidth = orig.ringInnerStrokeWidth;
906+
this.ringInnerStrokeWidthAttr = orig.ringInnerStrokeWidthAttr;
721907
this.ringRadius = orig.ringRadius;
908+
this.ringRadiusAttr = orig.ringRadiusAttr;
722909
this.ringInset = orig.ringInset;
910+
this.ringInsetAttr = orig.ringInsetAttr;
723911
this.ringInnerInset = orig.ringInnerInset;
912+
this.ringInnerInsetAttr = orig.ringInnerInsetAttr;
724913
this.ringShapeAppearance = orig.ringShapeAppearance;
725-
this.ringBoundsMode = orig.ringBoundsMode;
914+
this.ringShapeAppearanceResId = orig.ringShapeAppearanceResId;
915+
this.ringShapeAppearanceAttr = orig.ringShapeAppearanceAttr;
726916
if (orig.ringCustomBounds != null) {
727917
this.ringCustomBounds = new Rect(orig.ringCustomBounds);
728918
}

lib/java/com/google/android/material/focus/res/values/attrs.xml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
-->
1717
<resources>
1818

19-
<!-- The shape appearance of the focus ring. This is currently only
20-
supported from a theme / theme overlay. -->
21-
<attr name="focusRingsShapeAppearance" format="reference"/>
22-
2319
<declare-styleable name="FocusRingDrawable">
2420
<!-- Whether focus rings are enabled. -->
2521
<attr name="focusRingsEnabled" format="boolean"/>
@@ -39,5 +35,7 @@
3935
<attr name="focusRingsInnerStrokeInset" format="dimension"/>
4036
<!-- The radius of the focus ring. -->
4137
<attr name="focusRingsRadius" format="dimension"/>
38+
<!-- The shape appearance of the focus ring. -->
39+
<attr name="focusRingsShapeAppearance" format="reference"/>
4240
</declare-styleable>
4341
</resources>

0 commit comments

Comments
 (0)