From f87d208742d0e8c9ddd5fc80adee3875136d8ee4 Mon Sep 17 00:00:00 2001 From: arvin Date: Fri, 28 Apr 2023 22:24:49 +0800 Subject: [PATCH] fix RangeSlider, with no overlayShape shifts to the left (#125483) --- .../lib/src/material/slider_theme.dart | 95 ++++++++++--------- .../test/material/slider_theme_test.dart | 45 +++++++++ 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index 4123d72ba6..16f1f70fa4 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -1771,6 +1771,52 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS } } + +/// Base range slider track shape that provides an implementation of [getPreferredRect] for +/// default sizing. +/// +/// The height is set from [SliderThemeData.trackHeight] and the width of the +/// parent box less the larger of the widths of [SliderThemeData.rangeThumbShape] and +/// [SliderThemeData.overlayShape]. +/// +/// See also: +/// +/// * [RectangularRangeSliderTrackShape], which is a track shape with sharp +/// rectangular edges +mixin BaseRangeSliderTrackShape { + /// Returns a rect that represents the track bounds that fits within the + /// [Slider]. + /// + /// The width is the width of the [RangeSlider], but padded by the max + /// of the overlay and thumb radius. The height is defined by the [SliderThemeData.trackHeight]. + /// + /// The [Rect] is centered both horizontally and vertically within the slider + /// bounds. + Rect getPreferredRect({ + required RenderBox parentBox, + Offset offset = Offset.zero, + required SliderThemeData sliderTheme, + bool isEnabled = false, + bool isDiscrete = false, + }) { + assert(sliderTheme.rangeThumbShape != null); + assert(sliderTheme.overlayShape != null); + assert(sliderTheme.trackHeight != null); + final double thumbWidth = sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete).width; + final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width; + final double trackHeight = sliderTheme.trackHeight!; + assert(overlayWidth >= 0); + assert(trackHeight >= 0); + + final double trackLeft = offset.dx + math.max(overlayWidth / 2, thumbWidth / 2); + final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2; + final double trackRight = trackLeft + parentBox.size.width - math.max(thumbWidth, overlayWidth); + final double trackBottom = trackTop + trackHeight; + // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them. + return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom); + } +} + /// A [RangeSlider] track that's a simple rectangle. /// /// It paints a solid colored rectangle, vertically centered in the @@ -1798,35 +1844,13 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS /// the [RangeSlider]'s track. /// * [RoundedRectRangeSliderTrackShape], for a similar track with rounded /// edges. -class RectangularRangeSliderTrackShape extends RangeSliderTrackShape { +class RectangularRangeSliderTrackShape extends RangeSliderTrackShape with BaseRangeSliderTrackShape { /// Create a slider track with rectangular outer edges. /// /// The middle track segment is the selected range and is active, and the two /// outer track segments are inactive. const RectangularRangeSliderTrackShape(); - @override - Rect getPreferredRect({ - required RenderBox parentBox, - Offset offset = Offset.zero, - required SliderThemeData sliderTheme, - bool isEnabled = false, - bool isDiscrete = false, - }) { - assert(sliderTheme.overlayShape != null); - final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width; - final double trackHeight = sliderTheme.trackHeight!; - assert(overlayWidth >= 0); - assert(trackHeight >= 0); - - final double trackLeft = offset.dx + overlayWidth / 2; - final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2; - final double trackRight = trackLeft + parentBox.size.width - overlayWidth; - final double trackBottom = trackTop + trackHeight; - // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them. - return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom); - } - @override void paint( PaintingContext context, @@ -1913,36 +1937,13 @@ class RectangularRangeSliderTrackShape extends RangeSliderTrackShape { /// * [RangeSliderTrackShape], which can be used to create custom shapes for /// the [RangeSlider]'s track. /// * [RectangularRangeSliderTrackShape], for a similar track with sharp edges. -class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape { +class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape with BaseRangeSliderTrackShape { /// Create a slider track with rounded outer edges. /// /// The middle track segment is the selected range and is active, and the two /// outer track segments are inactive. const RoundedRectRangeSliderTrackShape(); - @override - Rect getPreferredRect({ - required RenderBox parentBox, - Offset offset = Offset.zero, - required SliderThemeData sliderTheme, - bool isEnabled = false, - bool isDiscrete = false, - }) { - assert(sliderTheme.overlayShape != null); - assert(sliderTheme.trackHeight != null); - final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width; - final double trackHeight = sliderTheme.trackHeight!; - assert(overlayWidth >= 0); - assert(trackHeight >= 0); - - final double trackLeft = offset.dx + overlayWidth / 2; - final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2; - final double trackRight = trackLeft + parentBox.size.width - overlayWidth; - final double trackBottom = trackTop + trackHeight; - // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them. - return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom); - } - @override void paint( PaintingContext context, diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index ec5d1eca05..d5c545511b 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -1430,6 +1430,51 @@ void main() { ); }); + // Regression test for https://github.com/flutter/flutter/issues/125467 + testWidgets('The RangeSlider track layout correctly when the overlay size is smaller than the thumb size', (WidgetTester tester) async { + final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith( + overlayShape: SliderComponentShape.noOverlay, + ); + + await tester.pumpWidget(_buildRangeApp(sliderTheme, values: const RangeValues(0.0, 1.0))); + + final MaterialInkController material = Material.of( + tester.element(find.byType(RangeSlider)), + ); + + // The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right + // (790 pixels from the left). The main check here it that the track itself should be centered on + // the 800 pixel-wide screen. + expect( + material, + paints + // active track RRect. Starts 10 pixels from left of screen. + ..rrect(rrect: RRect.fromLTRBAndCorners( + 10.0, + 298.0, + 10.0, + 302.0, + topLeft: const Radius.circular(2.0), + bottomLeft: const Radius.circular(2.0), + )) + // active track RRect Start 10 pixels from left screen. + ..rect(rect:const Rect.fromLTRB(10.0, 297.0, 790.0, 303.0),) + // inactive track RRect. Ends 10 pixels from right of screen. + ..rrect(rrect: RRect.fromLTRBAndCorners( + 790.0, + 298.0, + 790.0, + 302.0, + topRight: const Radius.circular(2.0), + bottomRight: const Radius.circular(2.0), + )) + // The thumb Left. + ..circle(x: 10.0, y: 300.0, radius: 10.0) + // The thumb Right. + ..circle(x: 790.0, y: 300.0, radius: 10.0), + ); + }); + // Only the thumb, overlay, and tick mark have special shortcuts to provide // no-op or empty shapes. //