Refactor: Move sliders value indicator shape to seperate file (#162858)

Refactor: Move sliders value indicator shape to seperate file
Part of: #162510 

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Kishan Rathore 2025-03-17 22:26:16 +05:30 committed by GitHub
parent 082a761570
commit 413fc7575f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 285 additions and 274 deletions

View File

@ -159,6 +159,7 @@ export 'src/material/selection_area.dart';
export 'src/material/shadows.dart';
export 'src/material/slider.dart';
export 'src/material/slider_theme.dart';
export 'src/material/slider_value_indicator_shape.dart';
export 'src/material/snack_bar.dart';
export 'src/material/snack_bar_theme.dart';
export 'src/material/spell_check_suggestions_toolbar.dart';

View File

@ -27,6 +27,7 @@ import 'constants.dart';
import 'debug.dart';
import 'material_state.dart';
import 'slider_theme.dart';
import 'slider_value_indicator_shape.dart';
import 'theme.dart';
// Examples can assume:

View File

@ -26,6 +26,7 @@ import 'debug.dart';
import 'material.dart';
import 'material_state.dart';
import 'slider_theme.dart';
import 'slider_value_indicator_shape.dart';
import 'theme.dart';
// Examples can assume:

View File

@ -2957,280 +2957,6 @@ class RoundSliderOverlayShape extends SliderComponentShape {
}
}
/// The default shape of a [Slider]'s value indicator.
///
/// ![A slider widget, consisting of 5 divisions and showing the rectangular slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_slider_value_indicator_shape.png)
///
/// See also:
///
/// * [Slider], which includes a value indicator defined by this shape.
/// * [SliderTheme], which can be used to configure the slider value indicator
/// of all sliders in a widget subtree.
class RectangularSliderValueIndicatorShape extends SliderComponentShape {
/// Create a slider value indicator that resembles a rectangular tooltip.
const RectangularSliderValueIndicatorShape();
static const _RectangularSliderValueIndicatorPathPainter _pathPainter =
_RectangularSliderValueIndicatorPathPainter();
@override
Size getPreferredSize(
bool isEnabled,
bool isDiscrete, {
TextPainter? labelPainter,
double? textScaleFactor,
}) {
assert(labelPainter != null);
assert(textScaleFactor != null && textScaleFactor >= 0);
return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
}
@override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final double scale = activationAnimation.value;
_pathPainter.paint(
parentBox: parentBox,
canvas: canvas,
center: center,
scale: scale,
labelPainter: labelPainter,
textScaleFactor: textScaleFactor,
sizeWithOverflow: sizeWithOverflow,
backgroundPaintColor: sliderTheme.valueIndicatorColor!,
strokePaintColor: sliderTheme.valueIndicatorStrokeColor,
);
}
}
/// The default shape of a [RangeSlider]'s value indicators.
///
/// ![A slider widget, consisting of 5 divisions and showing the rectangular range slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_range_slider_value_indicator_shape.png)
///
/// See also:
///
/// * [RangeSlider], which includes value indicators defined by this shape.
/// * [SliderTheme], which can be used to configure the range slider value
/// indicator of all sliders in a widget subtree.
class RectangularRangeSliderValueIndicatorShape extends RangeSliderValueIndicatorShape {
/// Create a range slider value indicator that resembles a rectangular tooltip.
const RectangularRangeSliderValueIndicatorShape();
static const _RectangularSliderValueIndicatorPathPainter _pathPainter =
_RectangularSliderValueIndicatorPathPainter();
@override
Size getPreferredSize(
bool isEnabled,
bool isDiscrete, {
required TextPainter labelPainter,
required double textScaleFactor,
}) {
assert(textScaleFactor >= 0);
return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
}
@override
double getHorizontalShift({
RenderBox? parentBox,
Offset? center,
TextPainter? labelPainter,
Animation<double>? activationAnimation,
double? textScaleFactor,
Size? sizeWithOverflow,
}) {
return _pathPainter.getHorizontalShift(
parentBox: parentBox!,
center: center!,
labelPainter: labelPainter!,
textScaleFactor: textScaleFactor!,
sizeWithOverflow: sizeWithOverflow!,
scale: activationAnimation!.value,
);
}
@override
void paint(
PaintingContext context,
Offset center, {
Animation<double>? activationAnimation,
Animation<double>? enableAnimation,
bool? isDiscrete,
bool? isOnTop,
TextPainter? labelPainter,
double? textScaleFactor,
Size? sizeWithOverflow,
RenderBox? parentBox,
SliderThemeData? sliderTheme,
TextDirection? textDirection,
double? value,
Thumb? thumb,
}) {
final Canvas canvas = context.canvas;
final double scale = activationAnimation!.value;
_pathPainter.paint(
parentBox: parentBox!,
canvas: canvas,
center: center,
scale: scale,
labelPainter: labelPainter!,
textScaleFactor: textScaleFactor!,
sizeWithOverflow: sizeWithOverflow!,
backgroundPaintColor: sliderTheme!.valueIndicatorColor!,
strokePaintColor:
isOnTop!
? sliderTheme.overlappingShapeStrokeColor
: sliderTheme.valueIndicatorStrokeColor,
);
}
}
class _RectangularSliderValueIndicatorPathPainter {
const _RectangularSliderValueIndicatorPathPainter();
static const double _triangleHeight = 8.0;
static const double _labelPadding = 16.0;
static const double _preferredHeight = 32.0;
static const double _minLabelWidth = 16.0;
static const double _bottomTipYOffset = 14.0;
static const double _preferredHalfHeight = _preferredHeight / 2;
static const double _upperRectRadius = 4;
Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
return Size(
_upperRectangleWidth(labelPainter, 1, textScaleFactor),
labelPainter.height + _labelPadding,
);
}
double getHorizontalShift({
required RenderBox parentBox,
required Offset center,
required TextPainter labelPainter,
required double textScaleFactor,
required Size sizeWithOverflow,
required 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 we use the bounds of the Overlay instead of the Slider.
final Offset globalCenter = parentBox.localToGlobal(center);
// The rectangle must be shifted towards 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, and if it 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);
}
}
double _upperRectangleWidth(TextPainter labelPainter, double scale, double textScaleFactor) {
final double unscaledWidth =
math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2;
return unscaledWidth * scale;
}
void paint({
required RenderBox parentBox,
required Canvas canvas,
required Offset center,
required double scale,
required TextPainter labelPainter,
required double textScaleFactor,
required Size sizeWithOverflow,
required Color backgroundPaintColor,
Color? strokePaintColor,
}) {
if (scale == 0.0) {
// Zero scale essentially means "do not draw anything", so it's safe to just return.
return;
}
assert(!sizeWithOverflow.isEmpty);
final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
final double horizontalShift = getHorizontalShift(
parentBox: parentBox,
center: center,
labelPainter: labelPainter,
textScaleFactor: textScaleFactor,
sizeWithOverflow: sizeWithOverflow,
scale: scale,
);
final double rectHeight = labelPainter.height + _labelPadding;
final Rect upperRect = Rect.fromLTWH(
-rectangleWidth / 2 + horizontalShift,
-_triangleHeight - rectHeight,
rectangleWidth,
rectHeight,
);
final Path trianglePath =
Path()
..lineTo(-_triangleHeight, -_triangleHeight)
..lineTo(_triangleHeight, -_triangleHeight)
..close();
final Paint fillPaint = Paint()..color = backgroundPaintColor;
final RRect upperRRect = RRect.fromRectAndRadius(
upperRect,
const Radius.circular(_upperRectRadius),
);
trianglePath.addRRect(upperRRect);
canvas.save();
// Prepare the canvas for the base of the tooltip, which is relative to the
// center of the thumb.
canvas.translate(center.dx, center.dy - _bottomTipYOffset);
canvas.scale(scale, scale);
if (strokePaintColor != null) {
final Paint strokePaint =
Paint()
..color = strokePaintColor
..strokeWidth = 1.0
..style = PaintingStyle.stroke;
canvas.drawPath(trianglePath, strokePaint);
}
canvas.drawPath(trianglePath, fillPaint);
// The label text is centered within the value indicator.
final double bottomTipToUpperRectTranslateY = -_preferredHalfHeight / 2 - upperRect.height;
canvas.translate(0, bottomTipToUpperRectTranslateY);
final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2);
final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2);
final Offset labelOffset = boxCenter - halfLabelPainterOffset;
labelPainter.paint(canvas, labelOffset);
canvas.restore();
}
}
/// A variant shape of a [Slider]'s value indicator . The value indicator is in
/// the shape of an upside-down pear.
///

View File

@ -0,0 +1,282 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'slider_theme.dart';
/// The default shape of a [Slider]'s value indicator.
///
/// ![A slider widget, consisting of 5 divisions and showing the rectangular slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_slider_value_indicator_shape.png)
///
/// See also:
///
/// * [Slider], which includes a value indicator defined by this shape.
/// * [SliderTheme], which can be used to configure the slider value indicator
/// of all sliders in a widget subtree.
class RectangularSliderValueIndicatorShape extends SliderComponentShape {
/// Create a slider value indicator that resembles a rectangular tooltip.
const RectangularSliderValueIndicatorShape();
static const _RectangularSliderValueIndicatorPathPainter _pathPainter =
_RectangularSliderValueIndicatorPathPainter();
@override
Size getPreferredSize(
bool isEnabled,
bool isDiscrete, {
TextPainter? labelPainter,
double? textScaleFactor,
}) {
assert(labelPainter != null);
assert(textScaleFactor != null && textScaleFactor >= 0);
return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
}
@override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final double scale = activationAnimation.value;
_pathPainter.paint(
parentBox: parentBox,
canvas: canvas,
center: center,
scale: scale,
labelPainter: labelPainter,
textScaleFactor: textScaleFactor,
sizeWithOverflow: sizeWithOverflow,
backgroundPaintColor: sliderTheme.valueIndicatorColor!,
strokePaintColor: sliderTheme.valueIndicatorStrokeColor,
);
}
}
/// The default shape of a [RangeSlider]'s value indicators.
///
/// ![A slider widget, consisting of 5 divisions and showing the rectangular range slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_range_slider_value_indicator_shape.png)
///
/// See also:
///
/// * [RangeSlider], which includes value indicators defined by this shape.
/// * [SliderTheme], which can be used to configure the range slider value
/// indicator of all sliders in a widget subtree.
class RectangularRangeSliderValueIndicatorShape extends RangeSliderValueIndicatorShape {
/// Create a range slider value indicator that resembles a rectangular tooltip.
const RectangularRangeSliderValueIndicatorShape();
static const _RectangularSliderValueIndicatorPathPainter _pathPainter =
_RectangularSliderValueIndicatorPathPainter();
@override
Size getPreferredSize(
bool isEnabled,
bool isDiscrete, {
required TextPainter labelPainter,
required double textScaleFactor,
}) {
assert(textScaleFactor >= 0);
return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
}
@override
double getHorizontalShift({
RenderBox? parentBox,
Offset? center,
TextPainter? labelPainter,
Animation<double>? activationAnimation,
double? textScaleFactor,
Size? sizeWithOverflow,
}) {
return _pathPainter.getHorizontalShift(
parentBox: parentBox!,
center: center!,
labelPainter: labelPainter!,
textScaleFactor: textScaleFactor!,
sizeWithOverflow: sizeWithOverflow!,
scale: activationAnimation!.value,
);
}
@override
void paint(
PaintingContext context,
Offset center, {
Animation<double>? activationAnimation,
Animation<double>? enableAnimation,
bool? isDiscrete,
bool? isOnTop,
TextPainter? labelPainter,
double? textScaleFactor,
Size? sizeWithOverflow,
RenderBox? parentBox,
SliderThemeData? sliderTheme,
TextDirection? textDirection,
double? value,
Thumb? thumb,
}) {
final Canvas canvas = context.canvas;
final double scale = activationAnimation!.value;
_pathPainter.paint(
parentBox: parentBox!,
canvas: canvas,
center: center,
scale: scale,
labelPainter: labelPainter!,
textScaleFactor: textScaleFactor!,
sizeWithOverflow: sizeWithOverflow!,
backgroundPaintColor: sliderTheme!.valueIndicatorColor!,
strokePaintColor:
isOnTop!
? sliderTheme.overlappingShapeStrokeColor
: sliderTheme.valueIndicatorStrokeColor,
);
}
}
class _RectangularSliderValueIndicatorPathPainter {
const _RectangularSliderValueIndicatorPathPainter();
static const double _triangleHeight = 8.0;
static const double _labelPadding = 16.0;
static const double _preferredHeight = 32.0;
static const double _minLabelWidth = 16.0;
static const double _bottomTipYOffset = 14.0;
static const double _preferredHalfHeight = _preferredHeight / 2;
static const double _upperRectRadius = 4;
Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
return Size(
_upperRectangleWidth(labelPainter, 1, textScaleFactor),
labelPainter.height + _labelPadding,
);
}
double getHorizontalShift({
required RenderBox parentBox,
required Offset center,
required TextPainter labelPainter,
required double textScaleFactor,
required Size sizeWithOverflow,
required 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 we use the bounds of the Overlay instead of the Slider.
final Offset globalCenter = parentBox.localToGlobal(center);
// The rectangle must be shifted towards 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, and if it 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);
}
}
double _upperRectangleWidth(TextPainter labelPainter, double scale, double textScaleFactor) {
final double unscaledWidth =
math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2;
return unscaledWidth * scale;
}
void paint({
required RenderBox parentBox,
required Canvas canvas,
required Offset center,
required double scale,
required TextPainter labelPainter,
required double textScaleFactor,
required Size sizeWithOverflow,
required Color backgroundPaintColor,
Color? strokePaintColor,
}) {
if (scale == 0.0) {
// Zero scale essentially means "do not draw anything", so it's safe to just return.
return;
}
assert(!sizeWithOverflow.isEmpty);
final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
final double horizontalShift = getHorizontalShift(
parentBox: parentBox,
center: center,
labelPainter: labelPainter,
textScaleFactor: textScaleFactor,
sizeWithOverflow: sizeWithOverflow,
scale: scale,
);
final double rectHeight = labelPainter.height + _labelPadding;
final Rect upperRect = Rect.fromLTWH(
-rectangleWidth / 2 + horizontalShift,
-_triangleHeight - rectHeight,
rectangleWidth,
rectHeight,
);
final Path trianglePath =
Path()
..lineTo(-_triangleHeight, -_triangleHeight)
..lineTo(_triangleHeight, -_triangleHeight)
..close();
final Paint fillPaint = Paint()..color = backgroundPaintColor;
final RRect upperRRect = RRect.fromRectAndRadius(
upperRect,
const Radius.circular(_upperRectRadius),
);
trianglePath.addRRect(upperRRect);
canvas.save();
// Prepare the canvas for the base of the tooltip, which is relative to the
// center of the thumb.
canvas.translate(center.dx, center.dy - _bottomTipYOffset);
canvas.scale(scale, scale);
if (strokePaintColor != null) {
final Paint strokePaint =
Paint()
..color = strokePaintColor
..strokeWidth = 1.0
..style = PaintingStyle.stroke;
canvas.drawPath(trianglePath, strokePaint);
}
canvas.drawPath(trianglePath, fillPaint);
// The label text is centered within the value indicator.
final double bottomTipToUpperRectTranslateY = -_preferredHalfHeight / 2 - upperRect.height;
canvas.translate(0, bottomTipToUpperRectTranslateY);
final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2);
final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2);
final Offset labelOffset = boxCenter - halfLabelPainterOffset;
labelPainter.paint(canvas, labelOffset);
canvas.restore();
}
}