Skip to content

Commit 1bf522f

Browse files
leticiarossipekingme
authored andcommitted
[Slider][a11y] Change thumb width when it's keyboard focused.
Refactored so that each thumb is an individual drawable, so that they can change width independently. Plus, with this change a custom thumb drawable no longer changes its width when pressed. PiperOrigin-RevId: 822162251
1 parent 3482899 commit 1bf522f

File tree

1 file changed

+131
-43
lines changed

1 file changed

+131
-43
lines changed

lib/java/com/google/android/material/slider/BaseSlider.java

Lines changed: 131 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,16 @@ abstract class BaseSlider<
398398
@NonNull private final RectF iconRectF = new RectF();
399399
@NonNull private final Rect iconRect = new Rect();
400400
@NonNull private final Matrix rotationMatrix = new Matrix();
401-
@NonNull private final MaterialShapeDrawable defaultThumbDrawable = new MaterialShapeDrawable();
401+
@NonNull private final List<MaterialShapeDrawable> defaultThumbDrawables = new ArrayList<>();
402+
402403
@Nullable private Drawable customThumbDrawable;
403404
@NonNull private List<Drawable> customThumbDrawablesForValues = Collections.emptyList();
404405

406+
private float thumbElevation;
407+
private float thumbStrokeWidth;
408+
@Nullable private ColorStateList thumbStrokeColor;
409+
@NonNull private ColorStateList thumbTintList;
410+
405411
private float touchPosition;
406412
@SeparationUnit private int separationUnit = UNIT_PX;
407413

@@ -494,10 +500,6 @@ public BaseSlider(
494500
setFocusable(true);
495501
setClickable(true);
496502

497-
// Set up the thumb drawable to always show the compat shadow.
498-
defaultThumbDrawable.setShadowCompatibilityMode(
499-
MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);
500-
501503
scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
502504

503505
accessibilityHelper = new AccessibilityHelper(this);
@@ -544,12 +546,12 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
544546

545547
valueFrom = a.getFloat(R.styleable.Slider_android_valueFrom, 0.0f);
546548
valueTo = a.getFloat(R.styleable.Slider_android_valueTo, 1.0f);
547-
setValues(valueFrom);
548549
setCentered(a.getBoolean(R.styleable.Slider_centered, false));
549550
stepSize = a.getFloat(R.styleable.Slider_android_stepSize, 0.0f);
550551
continuousModeTickCount = a.getInt(R.styleable.Slider_continuousModeTickCount, 0);
551552

552-
float defaultMinTouchTargetSize = MaterialAttributes.resolveMinimumAccessibleTouchTarget(context);
553+
float defaultMinTouchTargetSize =
554+
MaterialAttributes.resolveMinimumAccessibleTouchTarget(context);
553555
minTouchTargetSize =
554556
(int)
555557
Math.ceil(
@@ -578,8 +580,10 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
578580
context, R.color.material_slider_active_track_color));
579581
ColorStateList thumbColor =
580582
MaterialResources.getColorStateList(context, a, R.styleable.Slider_thumbColor);
581-
defaultThumbDrawable.setFillColor(thumbColor);
582-
583+
setThumbTintList(
584+
thumbColor != null
585+
? thumbColor
586+
: AppCompatResources.getColorStateList(context, R.color.material_slider_thumb_color));
583587
if (a.hasValue(R.styleable.Slider_thumbStrokeColor)) {
584588
setThumbStrokeColor(
585589
MaterialResources.getColorStateList(context, a, R.styleable.Slider_thumbStrokeColor));
@@ -660,6 +664,8 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
660664
setEnabled(false);
661665
}
662666

667+
setValues(valueFrom);
668+
663669
a.recycle();
664670
}
665671

@@ -899,6 +905,7 @@ private void setValuesInternal(@NonNull ArrayList<Float> values) {
899905

900906
this.values = values;
901907
dirtyConfig = true;
908+
updateDefaultThumbDrawables();
902909
// Only update the focused thumb index. The active thumb index will be updated on touch.
903910
focusedThumbIdx = 0;
904911
updateHaloHotspot();
@@ -907,6 +914,30 @@ private void setValuesInternal(@NonNull ArrayList<Float> values) {
907914
postInvalidate();
908915
}
909916

917+
private void updateDefaultThumbDrawables() {
918+
if (defaultThumbDrawables.size() != values.size()) {
919+
defaultThumbDrawables.clear();
920+
for (int i = 0; i < values.size(); i++) {
921+
// Create default thumbs to make sure each one is an independent drawable.
922+
defaultThumbDrawables.add(createNewDefaultThumb());
923+
}
924+
}
925+
}
926+
927+
private MaterialShapeDrawable createNewDefaultThumb() {
928+
MaterialShapeDrawable thumb = new MaterialShapeDrawable();
929+
thumb.setShadowCompatibilityMode(MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);
930+
thumb.setFillColor(getThumbTintList());
931+
thumb.setShapeAppearanceModel(
932+
ShapeAppearanceModel.builder().setAllCorners(ROUNDED, thumbWidth / 2f).build());
933+
thumb.setBounds(0, 0, thumbWidth, thumbHeight);
934+
thumb.setElevation(getThumbElevation());
935+
thumb.setStrokeWidth(getThumbStrokeWidth());
936+
thumb.setStrokeTint(getThumbStrokeColor());
937+
thumb.setState(getDrawableState());
938+
return thumb;
939+
}
940+
910941
private void createLabelPool() {
911942
// If there are too many labels, remove the extra ones from the end.
912943
if (labels.size() > values.size()) {
@@ -1093,12 +1124,17 @@ private Drawable initializeCustomThumbDrawable(Drawable originalDrawable) {
10931124
}
10941125

10951126
private void adjustCustomThumbDrawableBounds(Drawable drawable) {
1127+
adjustCustomThumbDrawableBounds(thumbWidth, drawable);
1128+
}
1129+
1130+
private void adjustCustomThumbDrawableBounds(
1131+
@IntRange(from = 0) @Px int width, Drawable drawable) {
10961132
int originalWidth = drawable.getIntrinsicWidth();
10971133
int originalHeight = drawable.getIntrinsicHeight();
10981134
if (originalWidth == -1 && originalHeight == -1) {
1099-
drawable.setBounds(0, 0, thumbWidth, thumbHeight);
1135+
drawable.setBounds(0, 0, width, thumbHeight);
11001136
} else {
1101-
float scaleRatio = (float) max(thumbWidth, thumbHeight) / max(originalWidth, originalHeight);
1137+
float scaleRatio = (float) max(width, thumbHeight) / max(originalWidth, originalHeight);
11021138
drawable.setBounds(
11031139
0, 0, (int) (originalWidth * scaleRatio), (int) (originalHeight * scaleRatio));
11041140
}
@@ -1201,7 +1237,7 @@ public void setLabelFormatter(@Nullable LabelFormatter formatter) {
12011237
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
12021238
*/
12031239
public float getThumbElevation() {
1204-
return defaultThumbDrawable.getElevation();
1240+
return thumbElevation;
12051241
}
12061242

12071243
/**
@@ -1211,7 +1247,13 @@ public float getThumbElevation() {
12111247
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
12121248
*/
12131249
public void setThumbElevation(float elevation) {
1214-
defaultThumbDrawable.setElevation(elevation);
1250+
if (elevation == thumbElevation) {
1251+
return;
1252+
}
1253+
thumbElevation = elevation;
1254+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1255+
defaultThumbDrawables.get(i).setElevation(thumbElevation);
1256+
}
12151257
}
12161258

12171259
/**
@@ -1296,16 +1338,26 @@ public void setThumbWidth(@IntRange(from = 0) @Px int width) {
12961338
}
12971339

12981340
thumbWidth = width;
1299-
1300-
defaultThumbDrawable.setShapeAppearanceModel(
1301-
ShapeAppearanceModel.builder().setAllCorners(ROUNDED, thumbWidth / 2f).build());
1302-
defaultThumbDrawable.setBounds(0, 0, thumbWidth, thumbHeight);
1303-
1341+
// Update custom thumbs, if any.
13041342
if (customThumbDrawable != null) {
1305-
adjustCustomThumbDrawableBounds(customThumbDrawable);
1343+
adjustCustomThumbDrawableBounds(width, customThumbDrawable);
13061344
}
1307-
for (Drawable customDrawable : customThumbDrawablesForValues) {
1308-
adjustCustomThumbDrawableBounds(customDrawable);
1345+
for (int i = 0; i < customThumbDrawablesForValues.size(); i++) {
1346+
adjustCustomThumbDrawableBounds(width, customThumbDrawablesForValues.get(i));
1347+
}
1348+
// Update default thumb(s).
1349+
setThumbWidth(width, /* thumbIndex= */ null);
1350+
}
1351+
1352+
private void setThumbWidth(@IntRange(from = 0) @Px int width, @Nullable Integer thumbIndex) {
1353+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1354+
if (thumbIndex == null || i == thumbIndex) {
1355+
defaultThumbDrawables
1356+
.get(i)
1357+
.setShapeAppearanceModel(
1358+
ShapeAppearanceModel.builder().setAllCorners(ROUNDED, thumbWidth / 2f).build());
1359+
defaultThumbDrawables.get(i).setBounds(0, 0, width, thumbHeight);
1360+
}
13091361
}
13101362

13111363
updateWidgetLayout(false);
@@ -1355,7 +1407,9 @@ public void setThumbHeight(@IntRange(from = 0) @Px int height) {
13551407

13561408
thumbHeight = height;
13571409

1358-
defaultThumbDrawable.setBounds(0, 0, thumbWidth, thumbHeight);
1410+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1411+
defaultThumbDrawables.get(i).setBounds(0, 0, thumbWidth, thumbHeight);
1412+
}
13591413

13601414
if (customThumbDrawable != null) {
13611415
adjustCustomThumbDrawableBounds(customThumbDrawable);
@@ -1390,7 +1444,15 @@ public void setThumbHeightResource(@DimenRes int height) {
13901444
* @see #getThumbStrokeColor()
13911445
*/
13921446
public void setThumbStrokeColor(@Nullable ColorStateList thumbStrokeColor) {
1393-
defaultThumbDrawable.setStrokeColor(thumbStrokeColor);
1447+
if (thumbStrokeColor == this.thumbStrokeColor) {
1448+
return;
1449+
}
1450+
1451+
this.thumbStrokeColor = thumbStrokeColor;
1452+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1453+
defaultThumbDrawables.get(i).setStrokeColor(thumbStrokeColor);
1454+
}
1455+
13941456
postInvalidate();
13951457
}
13961458

@@ -1418,8 +1480,9 @@ public void setThumbStrokeColorResource(@ColorRes int thumbStrokeColorResourceId
14181480
* @see #setThumbStrokeColor(ColorStateList)
14191481
* @see #setThumbStrokeColorResource(int)
14201482
*/
1483+
@Nullable
14211484
public ColorStateList getThumbStrokeColor() {
1422-
return defaultThumbDrawable.getStrokeColor();
1485+
return thumbStrokeColor;
14231486
}
14241487

14251488
/**
@@ -1432,7 +1495,15 @@ public ColorStateList getThumbStrokeColor() {
14321495
* @see #getThumbStrokeWidth()
14331496
*/
14341497
public void setThumbStrokeWidth(float thumbStrokeWidth) {
1435-
defaultThumbDrawable.setStrokeWidth(thumbStrokeWidth);
1498+
if (thumbStrokeWidth == this.thumbStrokeWidth) {
1499+
return;
1500+
}
1501+
1502+
this.thumbStrokeWidth = thumbStrokeWidth;
1503+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1504+
defaultThumbDrawables.get(i).setStrokeWidth(thumbStrokeWidth);
1505+
}
1506+
14361507
postInvalidate();
14371508
}
14381509

@@ -1460,7 +1531,7 @@ public void setThumbStrokeWidthResource(@DimenRes int thumbStrokeWidthResourceId
14601531
* @see #setThumbStrokeWidthResource(int)
14611532
*/
14621533
public float getThumbStrokeWidth() {
1463-
return defaultThumbDrawable.getStrokeWidth();
1534+
return thumbStrokeWidth;
14641535
}
14651536

14661537
/**
@@ -1708,7 +1779,7 @@ public void setHaloTintList(@NonNull ColorStateList haloColor) {
17081779
*/
17091780
@NonNull
17101781
public ColorStateList getThumbTintList() {
1711-
return defaultThumbDrawable.getFillColor();
1782+
return thumbTintList;
17121783
}
17131784

17141785
/**
@@ -1718,11 +1789,15 @@ public ColorStateList getThumbTintList() {
17181789
* @attr ref com.google.android.material.R.styleable#Slider_thumbColor
17191790
*/
17201791
public void setThumbTintList(@NonNull ColorStateList thumbColor) {
1721-
if (thumbColor.equals(defaultThumbDrawable.getFillColor())) {
1792+
if (thumbColor.equals(thumbTintList)) {
17221793
return;
17231794
}
17241795

1725-
defaultThumbDrawable.setFillColor(thumbColor);
1796+
thumbTintList = thumbColor;
1797+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
1798+
defaultThumbDrawables.get(i).setFillColor(thumbTintList);
1799+
}
1800+
17261801
invalidate();
17271802
}
17281803

@@ -3080,7 +3155,7 @@ private void drawThumbs(@NonNull Canvas canvas, int width, int yCenter) {
30803155
} else if (i < customThumbDrawablesForValues.size()) {
30813156
drawThumbDrawable(canvas, width, yCenter, value, customThumbDrawablesForValues.get(i));
30823157
} else {
3083-
// Clear out the track behind the thumb if we're in a disable state since the thumb is
3158+
// Clear out the track behind the thumb if we're in a disabled state since the thumb is
30843159
// transparent.
30853160
if (!isEnabled()) {
30863161
canvas.drawCircle(
@@ -3089,7 +3164,7 @@ private void drawThumbs(@NonNull Canvas canvas, int width, int yCenter) {
30893164
getThumbRadius(),
30903165
thumbPaint);
30913166
}
3092-
drawThumbDrawable(canvas, width, yCenter, value, defaultThumbDrawable);
3167+
drawThumbDrawable(canvas, width, yCenter, value, defaultThumbDrawables.get(i));
30933168
}
30943169
}
30953170
}
@@ -3224,13 +3299,7 @@ && abs(lastEvent.getY() - event.getY()) <= scaledTouchSlop) {
32243299
if (activeThumbIdx != -1) {
32253300
snapTouchPosition();
32263301
updateHaloHotspot();
3227-
// Reset the thumb width.
3228-
if (hasGapBetweenThumbAndTrack()
3229-
&& defaultThumbWidth != -1
3230-
&& defaultThumbTrackGapSize != -1) {
3231-
setThumbWidth(defaultThumbWidth);
3232-
setThumbTrackGapSize(defaultThumbTrackGapSize);
3233-
}
3302+
resetThumbWidth();
32343303
activeThumbIdx = -1;
32353304
onStopTrackingTouch();
32363305
}
@@ -3248,17 +3317,29 @@ && abs(lastEvent.getY() - event.getY()) <= scaledTouchSlop) {
32483317
}
32493318

32503319
private void updateThumbWidthWhenPressed() {
3251-
// Update thumb width and track gap size when pressed.
3252-
if (hasGapBetweenThumbAndTrack()) {
3320+
// Update default thumb width and track gap size when pressed.
3321+
if (hasGapBetweenThumbAndTrack()
3322+
&& customThumbDrawable == null
3323+
&& customThumbDrawablesForValues.isEmpty()) {
32533324
defaultThumbWidth = thumbWidth;
32543325
defaultThumbTrackGapSize = thumbTrackGapSize;
32553326
int pressedThumbWidth = Math.round(thumbWidth * THUMB_WIDTH_PRESSED_RATIO);
32563327
int delta = thumbWidth - pressedThumbWidth;
3257-
setThumbWidth(pressedThumbWidth);
3328+
// Only the currently pressed thumb should change width.
3329+
setThumbWidth(pressedThumbWidth, /* thumbIndex= */ activeThumbIdx);
32583330
setThumbTrackGapSize(thumbTrackGapSize - delta / 2);
32593331
}
32603332
}
32613333

3334+
private void resetThumbWidth() {
3335+
// Reset the default thumb width.
3336+
if (hasGapBetweenThumbAndTrack() && defaultThumbWidth != -1 && defaultThumbTrackGapSize != -1) {
3337+
// Only the currently pressed thumb should change width.
3338+
setThumbWidth(defaultThumbWidth, /* thumbIndex= */ activeThumbIdx);
3339+
setThumbTrackGapSize(defaultThumbTrackGapSize);
3340+
}
3341+
}
3342+
32623343
private double snapPosition(float position) {
32633344
if (stepSize > 0.0f) {
32643345
int stepCount = (int) ((valueTo - valueFrom) / stepSize);
@@ -3766,8 +3847,10 @@ protected void drawableStateChanged() {
37663847
label.setState(getDrawableState());
37673848
}
37683849
}
3769-
if (defaultThumbDrawable.isStateful()) {
3770-
defaultThumbDrawable.setState(getDrawableState());
3850+
for (int i = 0; i < defaultThumbDrawables.size(); i++) {
3851+
if (defaultThumbDrawables.get(i).isStateful()) {
3852+
defaultThumbDrawables.get(i).setState(getDrawableState());
3853+
}
37713854
}
37723855
haloPaint.setColor(getColorForState(haloColor));
37733856
haloPaint.setAlpha(HALO_ALPHA);
@@ -3802,6 +3885,7 @@ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
38023885
}
38033886

38043887
if (keyCode == KeyEvent.KEYCODE_TAB) {
3888+
resetThumbWidth();
38053889
if (event.hasNoModifiers()) {
38063890
return moveFocus(1);
38073891
}
@@ -3851,6 +3935,7 @@ private boolean moveFocus(int direction) {
38513935
return false;
38523936
}
38533937
activeThumbIdx = focusedThumbIdx;
3938+
updateThumbWidthWhenPressed();
38543939
updateHaloHotspot();
38553940
postInvalidate();
38563941
return true;
@@ -3918,6 +4003,7 @@ protected void onFocusChanged(
39184003
boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
39194004
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
39204005
if (!gainFocus) {
4006+
resetThumbWidth();
39214007
activeThumbIdx = -1;
39224008
accessibilityHelper.clearKeyboardFocusForVirtualView(focusedThumbIdx);
39234009
} else {
@@ -3927,6 +4013,8 @@ protected void onFocusChanged(
39274013
focusThumbOnFocusGained(direction);
39284014
activeThumbIdx = focusedThumbIdx;
39294015
}
4016+
resetThumbWidth();
4017+
updateThumbWidthWhenPressed();
39304018
accessibilityHelper.requestKeyboardFocusForVirtualView(focusedThumbIdx);
39314019
}
39324020
}

0 commit comments

Comments
 (0)