Add neck stretch (#15445)

During the review, we determined that it would be good for the value indicator to stretch when the text scale was smaller, and the shrink when it was larger, to keep the value visible over the finger at small sizes, and closer to the finger at large sizes. This implements that change.
This commit is contained in:
Greg Spencer 2018-03-12 20:28:34 -07:00 committed by GitHub
parent dc03398bc3
commit fc96326b9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 95 additions and 20 deletions

View file

@ -55,10 +55,10 @@ class SliderTheme extends InheritedWidget {
/// @override
/// State createState() => new LaunchState();
/// }
///
///
/// class LaunchState extends State<Launch> {
/// double _rocketThrust;
///
///
/// @override
/// Widget build(BuildContext context) {
/// return new SliderTheme(
@ -621,6 +621,11 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
static const double _thirtyDegrees = math.pi / 6.0;
static const Size _preferredSize =
const Size.fromHeight(_distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius);
// Set to true if you want a rectangle to be drawn around the label bubble.
// This helps with building tests that check that the label draws in the right
// place (because it prints the rect in the failed test output). It should not
// be checked in while set to "true".
static const bool _debuggingLabelLocation = false;
static Path _bottomLobePath; // Initialized by _generateBottomLobe
static Offset _bottomLobeEnd; // Initialized by _generateBottomLobe
@ -767,6 +772,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
final Path path = new Path();
final Offset bottomLobeEnd = _addBottomLobe(path);
// The base of the triangle between the top lobe center and the centers of
// the two top neck arcs.
final double neckTriangleBase = _topNeckRadius - bottomLobeEnd.dx;
@ -789,31 +795,54 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
);
final double leftNeckArcAngle = _ninetyDegrees - leftTheta;
final double rightNeckArcAngle = math.pi + _ninetyDegrees - rightTheta;
// The distance between the end of the bottom neck arc and the beginning of
// the top neck arc. We use this to shrink/expand it based on the scale
// factor of the value indicator.
final double neckStretchBaseline = bottomLobeEnd.dy - math.max(neckLeftCenter.dy, neckRightCenter.dy);
final double t = math.pow(inverseTextScale, 3.0);
final double stretch = (neckStretchBaseline * t).clamp(0.0, 10.0 * neckStretchBaseline);
final Offset neckStretch = new Offset(0.0, neckStretchBaseline - stretch);
assert(!_debuggingLabelLocation || () {
final Offset leftCenter = _topLobeCenter - new Offset(leftWidthNeeded, 0.0) + neckStretch;
final Offset rightCenter = _topLobeCenter + new Offset(rightWidthNeeded, 0.0) + neckStretch;
final Rect valueRect = new Rect.fromLTRB(
leftCenter.dx - _topLobeRadius,
leftCenter.dy - _topLobeRadius,
rightCenter.dx + _topLobeRadius,
rightCenter.dy + _topLobeRadius,
);
final Paint outlinePaint = new Paint()
..color = const Color(0xffff0000)
..style = PaintingStyle.stroke..strokeWidth = 1.0;
canvas.drawRect(valueRect, outlinePaint);
return true;
}());
_addArc(
path,
neckLeftCenter,
neckLeftCenter + neckStretch,
_topNeckRadius,
0.0,
-leftNeckArcAngle,
);
_addArc(
path,
_topLobeCenter - new Offset(leftWidthNeeded, 0.0),
_topLobeCenter - new Offset(leftWidthNeeded, 0.0) + neckStretch,
_topLobeRadius,
_ninetyDegrees + leftTheta,
_twoSeventyDegrees,
);
_addArc(
path,
_topLobeCenter + new Offset(rightWidthNeeded, 0.0),
_topLobeCenter + new Offset(rightWidthNeeded, 0.0) + neckStretch,
_topLobeRadius,
_twoSeventyDegrees,
_twoSeventyDegrees + math.pi - rightTheta,
);
_addArc(
path,
neckRightCenter,
neckRightCenter + neckStretch,
_topNeckRadius,
rightNeckArcAngle,
math.pi,
@ -822,7 +851,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
// Draw the label.
canvas.save();
canvas.translate(shift, -_distanceBetweenTopBottomCenters);
canvas.translate(shift, -_distanceBetweenTopBottomCenters + neckStretch.dy);
canvas.scale(inverseTextScale, inverseTextScale);
labelPainter.paint(canvas, Offset.zero - new Offset(labelHalfWidth, labelPainter.height / 2.0));
canvas.restore();

View file

@ -195,11 +195,11 @@ void main() {
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 6.0));
await tester.pumpWidget(buildApp(enabled: false));
await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
await tester.pumpAndSettle(); // wait for disable animation
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 4.0));
await tester.pumpWidget(buildApp(divisions: 3));
await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
await tester.pumpAndSettle(); // wait for disable animation
expect(
sliderBox,
paints
@ -210,7 +210,7 @@ void main() {
..circle(color: sliderTheme.thumbColor, radius: 6.0));
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
await tester.pumpAndSettle(); // wait for disable animation
expect(
sliderBox,
paints
@ -226,11 +226,11 @@ void main() {
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
Widget buildApp(String value, {double sliderValue = 0.5}) {
Widget buildApp(String value, {double sliderValue = 0.5, double textScale = 1.0}) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window),
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale),
child: new Material(
child: new Row(
children: <Widget>[
@ -258,9 +258,8 @@ void main() {
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
await tester.pump();
// Wait for value indicator animation to finish.
await tester.pump(const Duration(milliseconds: 500));
await tester.pumpAndSettle();
expect(
sliderBox,
paints
@ -280,9 +279,8 @@ void main() {
await tester.pumpWidget(buildApp('1000'));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
await tester.pump();
// Wait for value indicator animation to finish.
await tester.pump(const Duration(milliseconds: 500));
await tester.pumpAndSettle();
expect(
sliderBox,
paints
@ -301,9 +299,8 @@ void main() {
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
await tester.pump();
// Wait for value indicator animation to finish.
await tester.pump(const Duration(milliseconds: 500));
await tester.pumpAndSettle();
expect(
sliderBox,
paints
@ -322,9 +319,8 @@ void main() {
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
await tester.pump();
// Wait for value indicator animation to finish.
await tester.pump(const Duration(milliseconds: 500));
await tester.pumpAndSettle();
expect(
sliderBox,
paints
@ -338,5 +334,55 @@ void main() {
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
));
await gesture.up();
// Test that the neck stretches when the text scale gets smaller.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 0.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -49.0),
const Offset(90.0, -49.0),
const Offset(-24.0, -49.0),
],
excludes: <Offset>[
const Offset(98.0, -32.0), // inside full size, outside small
const Offset(-16.0, -32.0), // inside full size, outside small
const Offset(90.1, -49.0),
const Offset(-24.1, -49.0),
],
));
await gesture.up();
// Test that the neck shrinks when the text scale gets larger.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 2.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -38.8),
const Offset(98.0, -38.8),
const Offset(-16.0, -38.8),
const Offset(10.0, -23.0), // Inside large, outside scale=1.0
const Offset(-4.0, -23.0), // Inside large, outside scale=1.0
],
excludes: <Offset>[
const Offset(98.5, -38.8),
const Offset(-16.1, -38.8),
],
));
await gesture.up();
});
}