@@ -343,6 +343,7 @@ abstract class BaseSlider<
343
343
private int trackStopIndicatorSize ;
344
344
private int trackCornerSize ;
345
345
private int trackInsideCornerSize ;
346
+ private boolean centered = false ;
346
347
@ Nullable private Drawable trackIconActiveStart ;
347
348
private boolean trackIconActiveStartMutated = false ;
348
349
@ Nullable private Drawable trackIconActiveEnd ;
@@ -542,6 +543,7 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
542
543
valueFrom = a .getFloat (R .styleable .Slider_android_valueFrom , 0.0f );
543
544
valueTo = a .getFloat (R .styleable .Slider_android_valueTo , 1.0f );
544
545
setValues (valueFrom );
546
+ setCentered (a .getBoolean (R .styleable .Slider_centered , false ));
545
547
stepSize = a .getFloat (R .styleable .Slider_android_stepSize , 0.0f );
546
548
547
549
float defaultMinTouchTargetSize =
@@ -2396,6 +2398,30 @@ public void setOrientation(@Orientation int orientation) {
2396
2398
updateWidgetLayout (true );
2397
2399
}
2398
2400
2401
+ /**
2402
+ * Sets the slider to be in centered configuration, meaning the starting value is positioned in
2403
+ * the middle of the slider.
2404
+ *
2405
+ * @param isCentered boolean to use for the slider's centered configuration.
2406
+ * @attr ref com.google.android.material.R.styleable#Slider_centered
2407
+ * @see #isCentered()
2408
+ */
2409
+ public void setCentered (boolean isCentered ) {
2410
+ if (this .centered == isCentered ) {
2411
+ return ;
2412
+ }
2413
+ this .centered = isCentered ;
2414
+
2415
+ // if centered, the default value is at the center
2416
+ if (isCentered ) {
2417
+ setValues ((valueFrom + valueTo ) / 2f );
2418
+ } else {
2419
+ setValues (valueFrom );
2420
+ }
2421
+
2422
+ updateWidgetLayout (true );
2423
+ }
2424
+
2399
2425
@ Override
2400
2426
protected void onAttachedToWindow () {
2401
2427
super .onAttachedToWindow ();
@@ -2562,7 +2588,9 @@ protected void onDraw(@NonNull Canvas canvas) {
2562
2588
int yCenter = calculateTrackCenter ();
2563
2589
2564
2590
drawInactiveTracks (canvas , trackWidth , yCenter );
2565
- drawActiveTracks (canvas , trackWidth , yCenter );
2591
+ if (!isCentered ()) {
2592
+ drawActiveTracks (canvas , trackWidth , yCenter );
2593
+ }
2566
2594
2567
2595
if (isRtl () || isVertical ()) {
2568
2596
drawTrackIcons (canvas , activeTrackRect , inactiveTrackLeftRect );
@@ -2592,55 +2620,53 @@ private float[] getActiveRange() {
2592
2620
float left = normalizeValue (values .size () == 1 ? valueFrom : min );
2593
2621
float right = normalizeValue (max );
2594
2622
2623
+ // When centered, there is no active range, left == right in order to draw the inactive track on
2624
+ // both sides of the thumb leaving space for it, covering the entirety of the track.
2625
+ if (isCentered ()) {
2626
+ left = right ;
2627
+ }
2628
+
2595
2629
// In RTL we draw things in reverse, so swap the left and right range values
2596
2630
return isRtl () || isVertical () ? new float [] {right , left } : new float [] {left , right };
2597
2631
}
2598
2632
2599
2633
private void drawInactiveTracks (@ NonNull Canvas canvas , int width , int yCenter ) {
2600
- populateInactiveTrackRightRect (width , yCenter );
2601
- updateTrack (
2602
- canvas ,
2603
- inactiveTrackPaint ,
2604
- inactiveTrackRightRect ,
2605
- getTrackCornerSize (),
2606
- FullCornerDirection .RIGHT );
2607
-
2608
- // Also draw inactive track to the left if there is any
2609
- populateInactiveTrackLeftRect (width , yCenter );
2610
- updateTrack (
2634
+ float [] activeRange = getActiveRange ();
2635
+ float top = yCenter - trackThickness / 2f ;
2636
+ float bottom = yCenter + trackThickness / 2f ;
2637
+
2638
+ drawInactiveTrackSection (
2639
+ trackSidePadding - getTrackCornerSize (),
2640
+ trackSidePadding + activeRange [0 ] * width - thumbTrackGapSize ,
2641
+ top ,
2642
+ bottom ,
2611
2643
canvas ,
2612
- inactiveTrackPaint ,
2613
2644
inactiveTrackLeftRect ,
2614
- getTrackCornerSize (),
2615
2645
FullCornerDirection .LEFT );
2646
+ drawInactiveTrackSection (
2647
+ trackSidePadding + activeRange [1 ] * width + thumbTrackGapSize ,
2648
+ trackSidePadding + width + getTrackCornerSize (),
2649
+ top ,
2650
+ bottom ,
2651
+ canvas ,
2652
+ inactiveTrackRightRect ,
2653
+ FullCornerDirection .RIGHT );
2616
2654
}
2617
2655
2618
- private void populateInactiveTrackRightRect (int width , int yCenter ) {
2619
- float [] activeRange = getActiveRange ();
2620
- float right = trackSidePadding + activeRange [1 ] * width ;
2621
- if (right < trackSidePadding + width ) {
2622
- inactiveTrackRightRect .set (
2623
- right + thumbTrackGapSize ,
2624
- yCenter - trackThickness / 2f ,
2625
- trackSidePadding + width + getTrackCornerSize (),
2626
- yCenter + trackThickness / 2f );
2627
- } else {
2628
- inactiveTrackRightRect .setEmpty ();
2629
- }
2630
- }
2631
-
2632
- private void populateInactiveTrackLeftRect (int width , int yCenter ) {
2633
- float [] activeRange = getActiveRange ();
2634
- float left = trackSidePadding + activeRange [0 ] * width ;
2635
- if (left > trackSidePadding ) {
2636
- inactiveTrackLeftRect .set (
2637
- trackSidePadding - getTrackCornerSize (),
2638
- yCenter - trackThickness / 2f ,
2639
- left - thumbTrackGapSize ,
2640
- yCenter + trackThickness / 2f );
2656
+ private void drawInactiveTrackSection (
2657
+ float from ,
2658
+ float to ,
2659
+ float top ,
2660
+ float bottom ,
2661
+ @ NonNull Canvas canvas ,
2662
+ RectF rect ,
2663
+ FullCornerDirection direction ) {
2664
+ if (to - from > getTrackCornerSize () - thumbTrackGapSize ) {
2665
+ rect .set (from , top , to , bottom );
2641
2666
} else {
2642
- inactiveTrackLeftRect .setEmpty ();
2667
+ rect .setEmpty ();
2643
2668
}
2669
+ updateTrack (canvas , inactiveTrackPaint , rect , getTrackCornerSize (), direction );
2644
2670
}
2645
2671
2646
2672
/**
@@ -2923,26 +2949,41 @@ private void maybeDrawTicks(@NonNull Canvas canvas) {
2923
2949
2924
2950
// Draw ticks on the left inactive track (if any).
2925
2951
if (leftActiveTickIndex > 0 ) {
2926
- canvas . drawPoints ( ticksCoordinates , 0 , leftActiveTickIndex * 2 , inactiveTicksPaint );
2952
+ drawTicks ( 0 , leftActiveTickIndex * 2 , canvas , inactiveTicksPaint );
2927
2953
}
2928
2954
2929
2955
// Draw ticks on the active track (if any).
2930
2956
if (leftActiveTickIndex <= rightActiveTickIndex ) {
2931
- canvas .drawPoints (
2932
- ticksCoordinates ,
2957
+ drawTicks (
2933
2958
leftActiveTickIndex * 2 ,
2934
- (rightActiveTickIndex - leftActiveTickIndex + 1 ) * 2 ,
2935
- activeTicksPaint );
2959
+ (rightActiveTickIndex + 1 ) * 2 ,
2960
+ canvas ,
2961
+ isCentered () ? inactiveTicksPaint : activeTicksPaint ); // centered uses inactive color.
2936
2962
}
2937
2963
2938
2964
// Draw ticks on the right inactive track (if any).
2939
2965
if ((rightActiveTickIndex + 1 ) * 2 < ticksCoordinates .length ) {
2940
- canvas .drawPoints (
2941
- ticksCoordinates ,
2942
- (rightActiveTickIndex + 1 ) * 2 ,
2943
- ticksCoordinates .length - (rightActiveTickIndex + 1 ) * 2 ,
2944
- inactiveTicksPaint );
2966
+ drawTicks (
2967
+ (rightActiveTickIndex + 1 ) * 2 , ticksCoordinates .length , canvas , inactiveTicksPaint );
2968
+ }
2969
+ }
2970
+
2971
+ private void drawTicks (int from , int to , Canvas canvas , Paint paint ) {
2972
+ for (int i = from ; i < to ; i += 2 ) {
2973
+ if (isOverlappingThumb (ticksCoordinates [i ])) {
2974
+ continue ;
2975
+ }
2976
+ canvas .drawPoint (ticksCoordinates [i ], ticksCoordinates [i + 1 ], paint );
2977
+ }
2978
+ }
2979
+
2980
+ private boolean isOverlappingThumb (float tickCoordinate ) {
2981
+ float threshold = thumbTrackGapSize + thumbWidth / 2f ;
2982
+ for (float value : values ) {
2983
+ float valueToX = valueToX (value );
2984
+ return tickCoordinate >= valueToX - threshold && tickCoordinate <= valueToX + threshold ;
2945
2985
}
2986
+ return false ;
2946
2987
}
2947
2988
2948
2989
private void maybeDrawStopIndicator (@ NonNull Canvas canvas , int yCenter ) {
@@ -2954,13 +2995,25 @@ private void maybeDrawStopIndicator(@NonNull Canvas canvas, int yCenter) {
2954
2995
if (values .get (values .size () - 1 ) < valueTo ) {
2955
2996
drawStopIndicator (canvas , valueToX (valueTo ), yCenter );
2956
2997
}
2957
- // Multiple thumbs, inactive track may be visible at the start.
2958
- if (values .size () > 1 && values .get (0 ) > valueFrom ) {
2998
+ // Centered, multiple thumbs, inactive track may be visible at the start.
2999
+ if (isCentered () || ( values .size () > 1 && values .get (0 ) > valueFrom ) ) {
2959
3000
drawStopIndicator (canvas , valueToX (valueFrom ), yCenter );
2960
3001
}
3002
+ // Centered, draw indicator in the middle of the track.
3003
+ if (isCentered ()) {
3004
+ drawStopIndicator (canvas , (valueToX (valueTo ) + valueToX (valueFrom )) / 2f , yCenter );
3005
+ }
2961
3006
}
2962
3007
2963
3008
private void drawStopIndicator (@ NonNull Canvas canvas , float x , float y ) {
3009
+ // Prevent drawing indicator on the thumbs.
3010
+ for (float value : values ) {
3011
+ float valueToX = valueToX (value );
3012
+ float threshold = thumbTrackGapSize + thumbWidth / 2f ;
3013
+ if (x >= valueToX - threshold && x <= valueToX + threshold ) {
3014
+ return ;
3015
+ }
3016
+ }
2964
3017
if (isVertical ()) {
2965
3018
canvas .drawPoint (y , x , stopIndicatorPaint );
2966
3019
} else {
@@ -3751,10 +3804,14 @@ final boolean isRtl() {
3751
3804
return getLayoutDirection () == View .LAYOUT_DIRECTION_RTL ;
3752
3805
}
3753
3806
3754
- final boolean isVertical () {
3807
+ public boolean isVertical () {
3755
3808
return widgetOrientation == VERTICAL ;
3756
3809
}
3757
3810
3811
+ public boolean isCentered () {
3812
+ return centered ;
3813
+ }
3814
+
3758
3815
/**
3759
3816
* Attempts to move focus to next or previous thumb <i>independent of layout direction</i> and
3760
3817
* returns whether the focused thumb changed. If focused thumb didn't change, we're at the view
0 commit comments