Fix Slider thumb doesn't align with divisions, thumb padding, and rounded corners (#149594)

fixes [[Slider] Thumb's center doesn't align with division's center](https://github.com/flutter/flutter/issues/62567)
fixes [Slider thumb doesn't respect round slider track shape](https://github.com/flutter/flutter/issues/149591)
fixes [`RoundedRectSliderTrackShape` corners are not rendered correctly](https://github.com/flutter/flutter/issues/149589)

(Verified these behaviors with Android components implementation)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  double _value = 5.0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        sliderTheme: const SliderThemeData(
          trackHeight: 32,
          thumbColor: Colors.green,
          activeTrackColor: Colors.deepPurple,
          inactiveTrackColor: Colors.amber,
        ),
      ),
      home: Scaffold(
        body: Slider(
          value: _value,
          // divisions: 10,
          // ignore: avoid_redundant_argument_values
          min: 0,
          max: 10,
          onChanged: (double value) {
            setState(() {
              _value = value;
            });
          },
        ),
      ),
    );
  }
}
```

</details>

### Description
This PR fixes several core `Sliders` issues which are apparent in https://github.com/flutter/flutter/pull/147783. As a result, fixing the these bugs will unblock it.

### 1. Fixes the thumb doesn't align with `Slider` divisions.

![Group 8](https://github.com/flutter/flutter/assets/48603081/9aa138ae-9525-4af4-8fc7-3cea0692a6d7)

![Group 9](https://github.com/flutter/flutter/assets/48603081/e97940ae-a1c8-4b8b-9971-1cf417d32e40)

### 2.  Fixes `RoundedRectSliderTrackShape` corners are not rendered correctly.

![Group 10](https://github.com/flutter/flutter/assets/48603081/ed20a6bb-d5c9-486b-a020-2c9ca7de55da)

### 3.  Fixes round track shape corners when the thumb is at the start or end of the round track shape.

![Group 4](https://github.com/flutter/flutter/assets/48603081/37a2e820-402d-4964-a206-717ccf1c5c02)

![Group 3](https://github.com/flutter/flutter/assets/48603081/5d36d523-5fb7-466f-9d53-b6928963fcab)

![Group 7](https://github.com/flutter/flutter/assets/48603081/8f3b4c48-f04d-4681-a62f-a7ea5a3e19fa)
This commit is contained in:
Taha Tesser 2024-07-15 10:09:09 +03:00 committed by GitHub
parent 82b63ff27d
commit 9e88446f89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 274 additions and 115 deletions

View File

@ -1650,7 +1650,22 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
sliderTheme: _sliderTheme,
isDiscrete: isDiscrete,
);
final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy);
final double padding = isDiscrete || _sliderTheme.trackShape!.isRounded ? trackRect.height : 0.0;
final double thumbPosition = isDiscrete
? trackRect.left + visualPosition * (trackRect.width - padding) + padding / 2
: trackRect.left + visualPosition * trackRect.width;
// Apply padding to trackRect.left and trackRect.right if the track height is
// greater than the thumb radius to ensure the thumb is drawn within the track.
final Size thumbSize = _sliderTheme.thumbShape!.getPreferredSize(isInteractive, isDiscrete);
final double thumbPadding = (padding > thumbSize.width / 2 ? padding / 2 : 0);
final Offset thumbCenter = Offset(
clampDouble(
thumbPosition,
trackRect.left + thumbPadding,
trackRect.right - thumbPadding,
),
trackRect.center.dy,
);
if (isInteractive) {
final Size overlaySize = sliderTheme.overlayShape!.getPreferredSize(isInteractive, false);
overlayRect = Rect.fromCircle(center: thumbCenter, radius: overlaySize.width / 2.0);
@ -1692,7 +1707,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
isEnabled: isInteractive,
sliderTheme: _sliderTheme,
).width;
final double padding = trackRect.height;
final double adjustedTrackWidth = trackRect.width - padding;
// If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {

View File

@ -1117,6 +1117,11 @@ abstract class SliderTrackShape {
bool isDiscrete,
required TextDirection textDirection,
});
/// Whether the track shape is rounded.
///
/// This is used to determine the correct position of the thumb in relation to the track.
bool get isRounded => false;
}
/// Base class for [RangeSlider] thumb shapes.
@ -1534,6 +1539,10 @@ mixin BaseSliderTrackShape {
// If the parentBox's 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);
}
/// Whether the track shape is rounded. This is used to determine the correct
/// position of the thumb in relation to the track. Defaults to false.
bool get isRounded => false;
}
/// A [Slider] track that's a simple rectangle.
@ -1714,39 +1723,45 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS
);
final Radius trackRadius = Radius.circular(trackRect.height / 2);
final Radius activeTrackRadius = Radius.circular((trackRect.height + additionalActiveTrackHeight) / 2);
final bool isLTR = textDirection == TextDirection.ltr;
final bool isRTL = textDirection == TextDirection.rtl;
context.canvas.drawRRect(
RRect.fromLTRBAndCorners(
trackRect.left,
(textDirection == TextDirection.ltr) ? trackRect.top - (additionalActiveTrackHeight / 2): trackRect.top,
thumbCenter.dx,
(textDirection == TextDirection.ltr) ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
topLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius : trackRadius,
bottomLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius: trackRadius,
),
leftTrackPaint,
);
context.canvas.drawRRect(
RRect.fromLTRBAndCorners(
thumbCenter.dx,
(textDirection == TextDirection.rtl) ? trackRect.top - (additionalActiveTrackHeight / 2) : trackRect.top,
trackRect.right,
(textDirection == TextDirection.rtl) ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
topRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius,
bottomRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius,
),
rightTrackPaint,
);
final bool drawInactiveTrack = thumbCenter.dx < (trackRect.right - (sliderTheme.trackHeight! / 2));
if (drawInactiveTrack) {
// Draw the inactive track segment.
context.canvas.drawRRect(
RRect.fromLTRBR(
thumbCenter.dx - (sliderTheme.trackHeight! / 2),
isRTL ? trackRect.top - (additionalActiveTrackHeight / 2) : trackRect.top,
trackRect.right,
isRTL ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
isLTR ? trackRadius : activeTrackRadius,
),
rightTrackPaint,
);
}
final bool drawActiveTrack = thumbCenter.dx > (trackRect.left + (sliderTheme.trackHeight! / 2));
if (drawActiveTrack) {
// Draw the active track segment.
context.canvas.drawRRect(
RRect.fromLTRBR(
trackRect.left,
isLTR ? trackRect.top - (additionalActiveTrackHeight / 2): trackRect.top,
thumbCenter.dx + (sliderTheme.trackHeight! / 2),
isLTR ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
isLTR ? activeTrackRadius : trackRadius,
),
leftTrackPaint,
);
}
final bool showSecondaryTrack = (secondaryOffset != null) &&
((textDirection == TextDirection.ltr)
? (secondaryOffset.dx > thumbCenter.dx)
: (secondaryOffset.dx < thumbCenter.dx));
(isLTR ? (secondaryOffset.dx > thumbCenter.dx) : (secondaryOffset.dx < thumbCenter.dx));
if (showSecondaryTrack) {
final ColorTween secondaryTrackColorTween = ColorTween(begin: sliderTheme.disabledSecondaryActiveTrackColor, end: sliderTheme.secondaryActiveTrackColor);
final Paint secondaryTrackPaint = Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!;
if (textDirection == TextDirection.ltr) {
if (isLTR) {
context.canvas.drawRRect(
RRect.fromLTRBAndCorners(
thumbCenter.dx,
@ -1773,6 +1788,9 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS
}
}
}
@override
bool get isRounded => true;
}

View File

@ -558,7 +558,7 @@ void main() {
await tester.tap(find.text('push wrapped'));
await tester.pumpAndSettle(); // route animation
RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(sliderBox, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor));
expect(sliderBox, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor));
expect(sliderBox, paints..circle(color: thumbColor));
Navigator.of(navigatorContext).pop();
@ -567,7 +567,7 @@ void main() {
await tester.tap(find.text('push unwrapped'));
await tester.pumpAndSettle(); // route animation
sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(sliderBox, isNot(paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)));
expect(sliderBox, isNot(paints..circle(color: thumbColor)));
});

View File

@ -167,7 +167,7 @@ void main() {
expect(value, equals(0.20));
expect(log.length, 1);
expect(log[0], const Offset(212.0, 300.0));
expect(log[0], const Offset(213.0, 300.0));
});
testWidgets('Slider can move when tapped (LTR)', (WidgetTester tester) async {
@ -417,8 +417,8 @@ void main() {
);
final List<Offset> expectedLog = <Offset>[
const Offset(24.0, 300.0),
const Offset(24.0, 300.0),
const Offset(26.0, 300.0),
const Offset(26.0, 300.0),
const Offset(400.0, 300.0),
];
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
@ -439,13 +439,13 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
expect(value, equals(0.0));
expect(log.length, 7);
expect(log.last.dx, moreOrLessEquals(344.5, epsilon: 0.1));
expect(log.last.dx, moreOrLessEquals(344.8, epsilon: 0.1));
// Final position.
await tester.pump(const Duration(milliseconds: 80));
expectedLog.add(const Offset(24.0, 300.0));
expectedLog.add(const Offset(26.0, 300.0));
expect(value, equals(0.0));
expect(log.length, 8);
expect(log.last.dx, moreOrLessEquals(24.0, epsilon: 0.1));
expect(log.last.dx, moreOrLessEquals(26.0, epsilon: 0.1));
await gesture.up();
});
@ -490,7 +490,7 @@ void main() {
expect(updates, equals(1));
});
testWidgets('discrete Slider repaints when dragged', (WidgetTester tester) async {
testWidgets('Discrete Slider repaints when dragged', (WidgetTester tester) async {
final Key sliderKey = UniqueKey();
double value = 0.0;
final List<Offset> log = <Offset>[];
@ -526,8 +526,8 @@ void main() {
);
final List<Offset> expectedLog = <Offset>[
const Offset(24.0, 300.0),
const Offset(24.0, 300.0),
const Offset(26.0, 300.0),
const Offset(26.0, 300.0),
const Offset(400.0, 300.0),
];
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
@ -548,13 +548,13 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
expect(value, equals(0.0));
expect(log.length, 7);
expect(log.last.dx, moreOrLessEquals(344.5, epsilon: 0.1));
expect(log.last.dx, moreOrLessEquals(344.8, epsilon: 0.1));
// Final position.
await tester.pump(const Duration(milliseconds: 80));
expectedLog.add(const Offset(24.0, 300.0));
expectedLog.add(const Offset(26.0, 300.0));
expect(value, equals(0.0));
expect(log.length, 8);
expect(log.last.dx, moreOrLessEquals(24.0, epsilon: 0.1));
expect(log.last.dx, moreOrLessEquals(26.0, epsilon: 0.1));
await gesture.up();
});
@ -1175,7 +1175,7 @@ void main() {
..circle(x: 400.0, y: 24.0, radius: 1.0)
..circle(x: 587.0, y: 24.0, radius: 1.0)
..circle(x: 774.0, y: 24.0, radius: 1.0)
..circle(x: 24.0, y: 24.0, radius: 10.0),
..circle(x: 26.0, y: 24.0, radius: 10.0),
);
gesture = await tester.startGesture(center);
@ -1186,13 +1186,13 @@ void main() {
expect(
material,
paints
..circle(x: 111.20703125, y: 24.0, radius: 5.687664985656738)
..circle(x: 112.7431640625, y: 24.0, radius: 5.687664985656738)
..circle(x: 26.0, y: 24.0, radius: 1.0)
..circle(x: 213.0, y: 24.0, radius: 1.0)
..circle(x: 400.0, y: 24.0, radius: 1.0)
..circle(x: 587.0, y: 24.0, radius: 1.0)
..circle(x: 774.0, y: 24.0, radius: 1.0)
..circle(x: 111.20703125, y: 24.0, radius: 10.0),
..circle(x: 112.7431640625, y: 24.0, radius: 10.0),
);
// Reparenting in the middle of an animation should do nothing.
@ -1206,13 +1206,13 @@ void main() {
expect(
material,
paints
..circle(x: 190.0135726928711, y: 24.0, radius: 12.0)
..circle(x: 191.130521774292, y: 24.0, radius: 12.0)
..circle(x: 26.0, y: 24.0, radius: 1.0)
..circle(x: 213.0, y: 24.0, radius: 1.0)
..circle(x: 400.0, y: 24.0, radius: 1.0)
..circle(x: 587.0, y: 24.0, radius: 1.0)
..circle(x: 774.0, y: 24.0, radius: 1.0)
..circle(x: 190.0135726928711, y: 24.0, radius: 10.0),
..circle(x: 191.130521774292, y: 24.0, radius: 10.0),
);
// Wait for animations to finish.
await tester.pumpAndSettle();
@ -3249,11 +3249,11 @@ void main() {
expect(
renderObject,
paints
// active track RRect
..rrect(rrect: RRect.fromLTRBAndCorners(-14.0, 2.0, 5.0, 8.0, topLeft: const Radius.circular(3.0), bottomLeft: const Radius.circular(3.0)))
// inactive track RRect
..rrect(rrect: RRect.fromLTRBAndCorners(5.0, 3.0, 24.0, 7.0, topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0)))
// thumb
// Inactive track RRect.
..rrect(rrect: RRect.fromLTRBR(3.0, 3.0, 24.0, 7.0, const Radius.circular(2.0)))
// Active track RRect.
..rrect(rrect: RRect.fromLTRBR(-14.0, 2.0, 7.0, 8.0, const Radius.circular(3.0)))
// Thumb.
..circle(x: 5.0, y: 5.0, radius: 10.0, ),
);
});
@ -3285,7 +3285,7 @@ void main() {
await tester.pumpAndSettle(); // Finish the animation.
late RRect activeTrackRRect;
expect(renderObject, paints..something((Symbol method, List<dynamic> arguments) {
expect(renderObject, paints..rrect()..something((Symbol method, List<dynamic> arguments) {
if (method != #drawRRect) {
return false;
}
@ -3293,10 +3293,18 @@ void main() {
return true;
}));
const double padding = 4.0;
// The thumb should at one-third(5 / 15) of the Slider.
// The right of the active track shape is the position of the thumb.
// 24.0 is the default margin, (800.0 - 24.0 - 24.0) is the slider's width.
expect(nearEqual(activeTrackRRect.right, (800.0 - 24.0 - 24.0) * (5 / 15) + 24.0, 0.01), true);
expect(
nearEqual(
activeTrackRRect.right,
(800.0 - 24.0 - 24.0 + (padding / 2)) * (5 / 15) + 24.0 + padding / 2,
0.01,
),
true,
);
});
testWidgets('Slider paints thumbColor', (WidgetTester tester) async {

View File

@ -165,12 +165,20 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(360.4, 298.0, 776.0, 302.0, radius),
color: inactiveTrackColor,
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 364.4, 303.0, activatedRadius),
color: activeTrackColor,
),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
@ -182,7 +190,7 @@ void main() {
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
@ -204,8 +212,8 @@ void main() {
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: shadowColor)..circle(color: disabledThumbColor));
@ -307,7 +315,7 @@ void main() {
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: const Color(0xff000000)));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
@ -319,7 +327,7 @@ void main() {
// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
@ -330,7 +338,7 @@ void main() {
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
@ -340,7 +348,7 @@ void main() {
// Test setting only the secondaryActiveColor.
await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1));
expect(material, paints..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
@ -350,7 +358,7 @@ void main() {
// Test setting both activeColor, inactiveColor, and secondaryActiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(material, paints..rrect(color: customColor2)..rrect(color: customColor1)..rrect(color: customColor3));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
@ -361,7 +369,7 @@ void main() {
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(
material,
paints
@ -384,7 +392,7 @@ void main() {
secondaryActiveColor: customColor3,
divisions: 3,
));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(material, paints..rrect(color: customColor2)..rrect(color: customColor1)..rrect(color: customColor3));
expect(
material,
paints
@ -409,8 +417,8 @@ void main() {
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
@ -443,8 +451,8 @@ void main() {
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
@ -485,8 +493,8 @@ void main() {
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: customColor1) // active track
..rrect(color: customColor2) // inactive track
..rrect(color: customColor2) // Inactive track
..rrect(color: customColor1) // Active track
..circle(color: customColor1.withOpacity(0.12)) // overlay
..circle(color: customColor2) // 1st tick mark
..circle(color: customColor2) // 2nd tick mark
@ -549,7 +557,7 @@ void main() {
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Test Slider parameters.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..circle(color: thumbColor));
} finally {
debugDisableShadows = true;
@ -712,9 +720,23 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.secondaryActiveTrackColor),
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(210.0, 298.0, 776.0, 302.0, radius),
color: sliderTheme.inactiveTrackColor,
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 214.0, 303.0, activatedRadius),
color: sliderTheme.activeTrackColor,
)
..rrect(
rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0,
topRight: radius,
bottomRight: radius,
),
color: sliderTheme.secondaryActiveTrackColor,
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5, enabled: false));
@ -724,9 +746,23 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledSecondaryActiveTrackColor),
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(210.0, 298.0, 776.0, 302.0, radius),
color: sliderTheme.disabledInactiveTrackColor,
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 214.0, 303.0, activatedRadius),
color: sliderTheme.disabledActiveTrackColor,
)
..rrect(
rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0,
topRight: radius,
bottomRight: radius,
),
color: sliderTheme.disabledSecondaryActiveTrackColor,
),
);
});
@ -1276,8 +1312,16 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(204.0, 292.0, 776.0, 308.0, radius),
color: sliderTheme.inactiveTrackColor,
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 291.0, 220.0, 309.0, activatedRadius),
color: sliderTheme.activeTrackColor,
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
@ -1288,8 +1332,16 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(204.0, 292.0, 776.0, 308.0, radius),
color: sliderTheme.disabledInactiveTrackColor,
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 291.0, 220.0, 309.0, activatedRadius),
color: sliderTheme.disabledActiveTrackColor,
),
);
});
@ -1422,23 +1474,21 @@ void main() {
expect(
material,
paints
// active track RRect. Starts 10 pixels from left of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
10.0,
297.0,
400.0,
303.0,
topLeft: const Radius.circular(3.0),
bottomLeft: const Radius.circular(3.0),
))
// inactive track RRect. Ends 10 pixels from right of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
400.0,
// Inactive track RRect. Ends 10 pixels from right of screen.
..rrect(rrect: RRect.fromLTRBR(
398.0,
298.0,
790.0,
302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
const Radius.circular(2.0),
))
// Active track RRect. Starts 10 pixels from left of screen.
..rrect(rrect: RRect.fromLTRBR(
10.0,
297.0,
402.0,
303.0,
const Radius.circular(3.0),
))
// The thumb.
..circle(x: 400.0, y: 300.0, radius: 10.0),
@ -1882,11 +1932,12 @@ void main() {
testWidgets('activeTrackRadius is taken into account when painting the border of the active track', (WidgetTester tester) async {
await tester.pumpWidget(_buildApp(
value: 0.5,
ThemeData().sliderTheme.copyWith(
trackShape: const RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight(
additionalActiveTrackHeight: 10.0
)
)
additionalActiveTrackHeight: 10.0,
),
),
));
await tester.pumpAndSettle();
final Offset center = tester.getCenter(find.byType(Slider));
@ -1894,16 +1945,10 @@ void main() {
expect(
find.byType(Slider),
paints
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 293.0, 24.0, 307.0,
topLeft: const Radius.circular(7.0),
bottomLeft: const Radius.circular(7.0),
))
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 776.0, 302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
)),
// Inactive track.
..rrect(rrect: RRect.fromLTRBR(398.0, 298.0, 776.0, 302.0, const Radius.circular(2.0)))
// Active track.
..rrect(rrect: RRect.fromLTRBR(24.0, 293.0, 402.0, 307.0, const Radius.circular(7.0))),
);
// Finish gesture to release resources.
@ -2078,8 +2123,8 @@ void main() {
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffef7ff))
..rrect(color: const Color(0xff6750a4))
..rrect(color: const Color(0xffe6e0e9))
..rrect(color: const Color(0xff6750a4))
..path(color: Color(theme.colorScheme.primary.value))
);
@ -2434,6 +2479,75 @@ void main() {
await gesture.up();
});
group('RoundedRectSliderTrackShape', () {
testWidgets('Only draw active track if thumb center is higher than trackRect.left and track radius', (WidgetTester tester) async {
const SliderThemeData sliderTheme = SliderThemeData(trackShape: RoundedRectSliderTrackShape());
await tester.pumpWidget(_buildApp(sliderTheme));
MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(22.0, 298.0, 776.0, 302.0, const Radius.circular(2.0)),
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.025));
material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(40.8, 298.0, 776.0, 302.0, const Radius.circular(2.0)),
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 44.8, 303.0, const Radius.circular(3.0)),
),
);
});
testWidgets('Only draw inactive track if thumb center is lower than trackRect.right and track radius', (WidgetTester tester) async {
const SliderThemeData sliderTheme = SliderThemeData(trackShape: RoundedRectSliderTrackShape());
await tester.pumpWidget(_buildApp(sliderTheme, value: 1.0));
MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 778.0, 303.0, const Radius.circular(3.0)),
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.975));
material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
// Inactive track.
..rrect(
rrect: RRect.fromLTRBR(755.2, 298.0, 776.0, 302.0, const Radius.circular(2.0)),
)
// Active track.
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 759.2, 303.0, const Radius.circular(3.0)),
),
);
});
});
testWidgets('SliderTrackShape isRounded defaults', (WidgetTester tester) async {
expect(const RectangularSliderTrackShape().isRounded, isFalse);
expect(const RoundedRectSliderTrackShape().isRounded, isTrue);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
@ -2500,12 +2614,17 @@ void main() {
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
..rrect(
rrect: RRect.fromLTRBR(360.4, 298.0, 776.0, 302.0, radius),
color: inactiveTrackColor)
..rrect(
rrect: RRect.fromLTRBR(24.0, 297.0, 364.4, 303.0, activatedRadius),
color: activeTrackColor,
),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
@ -2517,7 +2636,7 @@ void main() {
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..rrect(color: inactiveTrackColor)..rrect(color: activeTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
@ -2539,8 +2658,8 @@ void main() {
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: disabledThumbColor));
@ -2634,8 +2753,8 @@ void main() {
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: const Color(0xff2196f3))
..rrect(color: const Color(0x3d2196f3))
..rrect(color: const Color(0xff2196f3))
// Test that the value indicator text is painted with the correct color.
..path(color: const Color(0xf55f5f5f))
);