Flutter lets you create beautiful, natively compiled applications. The reason Flutter can do this is because Flutter loves Material. Material is a design system that helps build high-quality, digital experiences. As UI design continues to evolve, Material continues to update its components, motion, and design system.
Flutter recently updated the Slider
and RangeSlider
widgets to the latest the Material guidelines. The new sliders were designed with better accessibility in mind: The tracks are taller, the thumbs have shadows, and the value indicators have a new shape and improved text scaling support.
This article describes the changes to the Slider
and RangeSlider
widgets.
What’s new?
Slider
and RangeSlider
were updated, to have a smoother experience when you work with these widgets. This diagram demonstrates the five different components that go into making the slider widgets.
Both Slider
and RangeSlider
consist of five parts:
- Value indicators that show labels for thumb values
- A track that the thumbs slide across
- Thumbs that indicate the position of the value
- Overlays that display when the thumbs are pressed
- Tick marks on the track when the slider is discrete
Note: This article refers to two different types of overlays:
- The
OverlaySlider
component represents the component mentioned in the preceding image. - Flutter’s
Overlay
class is used to ‘float’ widgets over all the other widgets shown on the following screen.
New default value indicator shape
The biggest visual difference between the old slider and the new slider widgets is the default value indicators: RectangularSliderValueIndicatorShape
for Slider
and RectangularRangeSliderValueIndicatorShape
for RangeSlider
. The indicators reflect the latest design changes from Material. The old indicator shape was an upside-down pear, and the new shape is more rectangular and looks like a speech bubble. The value indicator was updated for both Slider
and RangeSlider
.
Value indicator paints on the overlay
The updated value indicator is now painted on the overlay. This means that the indicator is no longer restricted to the scope of the slider’s MediaQuery
and can be displayed over other widgets. This is expected because the indicator is only activated when dealing with the gesture detector, which means that it’s only shown when the user interacts with the slider. By integrating the Overlay
with the CompositedTransformTarget
, CompositedTransformFollower
, and Layerlink
widgets, the value indicator can display over other widgets. To learn more about this mechanism, see How to float an overlay widget over a (possibly transformed) UI widget.
The following diagram shows how the Slider
and RangeSlider
widgets are made:
You may notice that we use a separate LeafRenderObjectWidget
to paint the indicators on the overlay. This is because we need to paint on both the Overlay
class and the screen, which you can’t do from the same LeafRenderObjectWidget
widget.
Because we’re now painting the value indicator on the overlay, it’s no longer restricted to the scope of the MediaQuery
that contains the slider. The indicator is no longer clipped to that box, which means that the entire value indicator is displayed always.
Note: This example was made with the textScaleFactor
set to 4 for better demonstration.
The value indicator rectangle is shifted so that part of the indicator doesn’t go off screen. This improvement allows you to increase the number of digits within the indicator and increase the text scale factor of your application without worrying if the text exceeds the screen size. In the following image, the value indicator text scale is 4 and contains a 3-digit value, but the slider handles it with no problem.
double getHorizontalShift({ RenderBox parentBox, Offset center, TextPainter labelPainter, double textScaleFactor, Size sizeWithOverflow, double scale,}) { assert(!sizeWithOverflow.isEmpty); const double edgePadding = 8.0; final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor); /// Value indicator draws on the overlay, and by using the global Offset, /// we are making sure that we use the bounds of the Overlay instead of the Slider. final Offset globalCenter = parentBox.localToGlobal(center); // The rectangle must be shifted toward the center so that it minimizes the // chance of it rendering outside the bounds of the render box. If the shift // is negative, then the lobe is shifted from right to left. If the shift is // positive, then the lobe is shifted from left to right. final double overflowLeft = math.max(0, rectangleWidth / 2 — globalCenter.dx edgePadding); final double overflowRight = math.max(0, rectangleWidth / 2 — (sizeWithOverflow.width — globalCenter.dx — edgePadding)); if (rectangleWidth < sizeWithOverflow.width) { return overflowLeft — overflowRight; } else if (overflowLeft — overflowRight > 0) { return overflowLeft — (edgePadding * textScaleFactor); } else { return -overflowRight (edgePadding * textScaleFactor); }}
Note: This example was made with the textScaleFactor
set to 4 for better demonstration.
Activated value indicator takes precedence
The RangeSlider
value indicator was also updated so that the currently active indicator takes precedence over the resting indicator. This means that if the indicators overlap, then the active indicator ‘floats’ over the other one.
Note: This example was made with the textScaleFactor
set to 4 for better demonstration.
Old value indicator updated
We kept the PaddleValueIndicator
widget, and updated it to have better scalability and accessibility. The PaddleValueIndicator
widget is also printed on the Overlay
like the RectangularSliderValueIndicatorShape
. In the following example, you can see that both Slider
and RangeSlider
are using the old value indicator.
If you want to continue using the old value indicator, then just wrap your Slider
widget with SliderTheme
, and set the valueIndicatorShape
property in SliderThemeData
to PaddleSliderValueIndicatorShape
. The process is identical for RangeSlider
. Wrap your RangeSlider
widget with SliderTheme
, and set the rangeValueIndicatorShape
property in SliderThemeData
to PaddleRangeSliderValueIndicatorShape
.
The active track is bigger than the inactive track
The other component shapes in Slider
were also updated. The track shape was changed so that the active part of the track is bigger than the inactive part of the track. This means that if your application uses a left-to-right language, then the left part of the track is bigger than the right. If your application uses a right-to-left language, then the right part of the track is bigger than the left. For RangeSlider
, the active track is the portion of the track between the two thumbs.
Tick mark component
The tick mark size and positioning changed. The tick marks are now part of the track component instead of extending the end of the track. There is also padding on the tick mark, so it appears within the track component. The size of the tick mark now has a radius of 1.
Thumb component has a shadow
When the thumb slider component is activated, there is now a Shadow
that appears on the Overlay
.
Closing remarks
You can try these changes by switching to Flutter version 1.18.0–9.0. This version is available only on the master branch during the release of this article. You can switch to the master branch by running flutter channel master
and then flutter upgrade
.
Learn more about the development of the RangeSlider
in Material RangeSlider in Flutter.
About the author: Jose recently graduated from university and now works on Material, a design system that helps teams build high-quality digital experiences. Jose’s team maintains the Flutter material library. To learn more, visit his Jose’s pages on GitHub, LinkedIn, YouTube, and Instagram.