From ba0618d35dd06415d7ab8b2d2d36cab687886a66 Mon Sep 17 00:00:00 2001 From: gspencergoog Date: Thu, 12 Oct 2017 16:43:28 -0700 Subject: [PATCH] Make material slider respect textScaleFactor (#12511) Make material slider respect textScaleFactor Fixes #5938 --- packages/flutter/lib/src/material/slider.dart | 59 ++++++++++++++--- .../flutter/test/material/slider_test.dart | 65 +++++++++++++++++++ 2 files changed, 115 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 426f9acd7a..fd8eee8c85 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -170,11 +170,28 @@ class Slider extends StatefulWidget { } class _SliderState extends State with TickerProviderStateMixin { + _SliderState() { + _reactionController = new AnimationController( + duration: kRadialReactionDuration, + vsync: this, + ); + } + void _handleChanged(double value) { assert(widget.onChanged != null); widget.onChanged(value * (widget.max - widget.min) + widget.min); } + @override + void dispose() { + _reactionController?.dispose(); + super.dispose(); + } + + // Have to keep the reaction controller here so that we may dispose of it + // properly. + AnimationController _reactionController; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); @@ -187,8 +204,10 @@ class _SliderState extends State with TickerProviderStateMixin { inactiveColor: widget.inactiveColor ?? theme.unselectedWidgetColor, thumbOpenAtMin: widget.thumbOpenAtMin, textTheme: theme.accentTextTheme, + textScaleFactor: MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, vsync: this, + reactionController: _reactionController, ); } } @@ -203,8 +222,10 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { this.inactiveColor, this.thumbOpenAtMin, this.textTheme, + this.textScaleFactor, this.onChanged, this.vsync, + this.reactionController, }) : super(key: key); final double value; @@ -214,8 +235,10 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { final Color inactiveColor; final bool thumbOpenAtMin; final TextTheme textTheme; + final double textScaleFactor; final ValueChanged onChanged; final TickerProvider vsync; + final AnimationController reactionController; @override _RenderSlider createRenderObject(BuildContext context) { @@ -227,8 +250,10 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { inactiveColor: inactiveColor, thumbOpenAtMin: thumbOpenAtMin, textTheme: textTheme, + textScaleFactor: textScaleFactor, onChanged: onChanged, vsync: vsync, + reactionController: reactionController, textDirection: Directionality.of(context), ); } @@ -243,6 +268,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ..inactiveColor = inactiveColor ..thumbOpenAtMin = thumbOpenAtMin ..textTheme = textTheme + ..textScaleFactor = textScaleFactor ..onChanged = onChanged ..textDirection = Directionality.of(context); // Ticker provider cannot change since there's a 1:1 relationship between @@ -290,9 +316,11 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { Color inactiveColor, bool thumbOpenAtMin, TextTheme textTheme, + double textScaleFactor, ValueChanged onChanged, TickerProvider vsync, @required TextDirection textDirection, + @required AnimationController reactionController, }) : assert(value != null && value >= 0.0 && value <= 1.0), assert(textDirection != null), _label = label, @@ -302,6 +330,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { _inactiveColor = inactiveColor, _thumbOpenAtMin = thumbOpenAtMin, _textTheme = textTheme, + _textScaleFactor = textScaleFactor, _onChanged = onChanged, _textDirection = textDirection { _updateLabelPainter(); @@ -314,10 +343,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { _tap = new TapGestureRecognizer() ..team = team ..onTapUp = _handleTapUp; - _reactionController = new AnimationController( - duration: kRadialReactionDuration, - vsync: vsync, - ); + _reactionController = reactionController; _reaction = new CurvedAnimation( parent: _reactionController, curve: Curves.fastOutSlowIn @@ -396,6 +422,16 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { markNeedsPaint(); } + double get textScaleFactor => _textScaleFactor; + double _textScaleFactor; + set textScaleFactor(double value) { + if (value == _textScaleFactor) + return; + _textScaleFactor = value; + _updateLabelPainter(); + markNeedsPaint(); + } + ValueChanged get onChanged => _onChanged; ValueChanged _onChanged; set onChanged(ValueChanged value) { @@ -421,10 +457,9 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { void _updateLabelPainter() { if (label != null) { - // TODO(abarth): Handle textScaleFactor. https://github.com/flutter/flutter/issues/5938 _labelPainter ..text = new TextSpan( - style: _textTheme.body1.copyWith(fontSize: 10.0), + style: _textTheme.body1.copyWith(fontSize: 10.0 * _textScaleFactor), text: label, ) ..textDirection = textDirection @@ -628,9 +663,15 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { } if (label != null) { - final Offset center = new Offset(trackActive, _kLabelBalloonCenterTween.evaluate(_reaction) + trackCenter); - final double radius = _kLabelBalloonRadiusTween.evaluate(_reaction); - final Offset tip = new Offset(trackActive, _kLabelBalloonTipTween.evaluate(_reaction) + trackCenter); + final Offset center = new Offset( + trackActive, + _kLabelBalloonCenterTween.evaluate(_reaction) * textScaleFactor + trackCenter + ); + final double radius = _kLabelBalloonRadiusTween.evaluate(_reaction) * textScaleFactor; + final Offset tip = new Offset( + trackActive, + _kLabelBalloonTipTween.evaluate(_reaction) * textScaleFactor + trackCenter + ); final double tipAttachment = _kLabelBalloonTipAttachmentRatio * radius; canvas.drawCircle(center, radius, primaryPaint); diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index ee8019265b..16506acaa6 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -445,6 +445,71 @@ void main() { expect(tester.renderObject(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 32.0)); }); + testWidgets('discrete Slider respects textScaleFactor', (WidgetTester tester) async { + final Key sliderKey = new UniqueKey(); + double value = 0.0; + + Widget buildSlider({ double textScaleFactor }) { + return new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new MediaQuery( + data: new MediaQueryData(textScaleFactor: textScaleFactor), + child: new Material( + child: new Center( + child: new OverflowBox( + maxWidth: double.INFINITY, + maxHeight: double.INFINITY, + child: new Slider( + key: sliderKey, + min: 0.0, + max: 100.0, + divisions: 10, + label: '${value.round()}', + value: value, + onChanged: (double newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ), + ), + ); + }, + ), + ); + } + + await tester.pumpWidget(buildSlider(textScaleFactor: 1.0)); + Offset center = tester.getCenter(find.byType(Slider)); + TestGesture gesture = await tester.startGesture(center); + await gesture.moveBy(const Offset(10.0, 0.0)); + + expect( + tester.renderObject(find.byType(Slider)), + paints..circle(radius: 6.0, x: 16.0, y: 44.0) + ); + + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + + await tester.pumpWidget(buildSlider(textScaleFactor: 2.0)); + center = tester.getCenter(find.byType(Slider)); + gesture = await tester.startGesture(center); + await gesture.moveBy(const Offset(10.0, 0.0)); + + expect( + tester.renderObject(find.byType(Slider)), + paints..circle(radius: 12.0, x: 16.0, y: 44.0) + ); + + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + }); + testWidgets('Slider Semantics', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester);