Commit 7b549def authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add RTL support to Slider and CupertinoSlider (#11822)

Fixes #11358
parent 3437b770
...@@ -161,6 +161,7 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -161,6 +161,7 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget {
activeColor: activeColor, activeColor: activeColor,
onChanged: onChanged, onChanged: onChanged,
vsync: vsync, vsync: vsync,
textDirection: Directionality.of(context),
); );
} }
...@@ -170,7 +171,8 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -170,7 +171,8 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget {
..value = value ..value = value
..divisions = divisions ..divisions = divisions
..activeColor = activeColor ..activeColor = activeColor
..onChanged = onChanged; ..onChanged = onChanged
..textDirection = Directionality.of(context);
// Ticker provider cannot change since there's a 1:1 relationship between // Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object. // the _SliderRenderObjectWidget object and the _SliderState object.
} }
...@@ -192,11 +194,14 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -192,11 +194,14 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
Color activeColor, Color activeColor,
ValueChanged<double> onChanged, ValueChanged<double> onChanged,
TickerProvider vsync, TickerProvider vsync,
@required TextDirection textDirection,
}) : assert(value != null && value >= 0.0 && value <= 1.0), }) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(textDirection != null),
_value = value, _value = value,
_divisions = divisions, _divisions = divisions,
_activeColor = activeColor, _activeColor = activeColor,
_onChanged = onChanged, _onChanged = onChanged,
_textDirection = textDirection,
super(additionalConstraints: const BoxConstraints.tightFor(width: _kSliderWidth, height: _kSliderHeight)) { super(additionalConstraints: const BoxConstraints.tightFor(width: _kSliderWidth, height: _kSliderHeight)) {
_drag = new HorizontalDragGestureRecognizer() _drag = new HorizontalDragGestureRecognizer()
..onStart = _handleDragStart ..onStart = _handleDragStart
...@@ -251,6 +256,16 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -251,6 +256,16 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
markNeedsSemanticsUpdate(noGeometry: true); markNeedsSemanticsUpdate(noGeometry: true);
} }
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
return;
_textDirection = value;
markNeedsPaint();
}
AnimationController _position; AnimationController _position;
HorizontalDragGestureRecognizer _drag; HorizontalDragGestureRecognizer _drag;
...@@ -265,7 +280,18 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -265,7 +280,18 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
double get _trackLeft => _kPadding; double get _trackLeft => _kPadding;
double get _trackRight => size.width - _kPadding; double get _trackRight => size.width - _kPadding;
double get _thumbCenter => lerpDouble(_trackLeft + CupertinoThumbPainter.radius, _trackRight - CupertinoThumbPainter.radius, _value); double get _thumbCenter {
double visualPosition;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - _value;
break;
case TextDirection.ltr:
visualPosition = _value;
break;
}
return lerpDouble(_trackLeft + CupertinoThumbPainter.radius, _trackRight - CupertinoThumbPainter.radius, visualPosition);
}
bool get isInteractive => onChanged != null; bool get isInteractive => onChanged != null;
...@@ -279,7 +305,15 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -279,7 +305,15 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (isInteractive) { if (isInteractive) {
final double extent = math.max(_kPadding, size.width - 2.0 * (_kPadding + CupertinoThumbPainter.radius)); final double extent = math.max(_kPadding, size.width - 2.0 * (_kPadding + CupertinoThumbPainter.radius));
_currentDragValue += details.primaryDelta / extent; final double valueDelta = details.primaryDelta / extent;
switch (textDirection) {
case TextDirection.rtl:
_currentDragValue -= valueDelta;
break;
case TextDirection.ltr:
_currentDragValue += valueDelta;
break;
}
onChanged(_discretizedCurrentDragValue); onChanged(_discretizedCurrentDragValue);
} }
} }
...@@ -304,9 +338,21 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -304,9 +338,21 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas; double visualPosition;
Color leftColor;
final double value = _position.value; Color rightColor;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - _position.value;
leftColor = _kTrackColor;
rightColor = _activeColor;
break;
case TextDirection.ltr:
visualPosition = _position.value;
leftColor = _activeColor;
rightColor = _kTrackColor;
break;
}
final double trackCenter = offset.dy + size.height / 2.0; final double trackCenter = offset.dy + size.height / 2.0;
final double trackLeft = offset.dx + _trackLeft; final double trackLeft = offset.dx + _trackLeft;
...@@ -315,15 +361,16 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -315,15 +361,16 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
final double trackRight = offset.dx + _trackRight; final double trackRight = offset.dx + _trackRight;
final double trackActive = offset.dx + _thumbCenter; final double trackActive = offset.dx + _thumbCenter;
final Canvas canvas = context.canvas;
final Paint paint = new Paint(); final Paint paint = new Paint();
if (value > 0.0) { if (visualPosition > 0.0) {
paint.color = _activeColor; paint.color = rightColor;
canvas.drawRRect(new RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0), paint); canvas.drawRRect(new RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0), paint);
} }
if (value < 1.0) { if (visualPosition < 1.0) {
paint.color = _kTrackColor; paint.color = leftColor;
canvas.drawRRect(new RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0), paint); canvas.drawRRect(new RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0), paint);
} }
......
...@@ -229,6 +229,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -229,6 +229,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
textTheme: textTheme, textTheme: textTheme,
onChanged: onChanged, onChanged: onChanged,
vsync: vsync, vsync: vsync,
textDirection: Directionality.of(context),
); );
} }
...@@ -242,7 +243,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -242,7 +243,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..thumbOpenAtMin = thumbOpenAtMin ..thumbOpenAtMin = thumbOpenAtMin
..textTheme = textTheme ..textTheme = textTheme
..onChanged = onChanged; ..onChanged = onChanged
..textDirection = Directionality.of(context);
// Ticker provider cannot change since there's a 1:1 relationship between // Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object. // the _SliderRenderObjectWidget object and the _SliderState object.
} }
...@@ -290,14 +292,17 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -290,14 +292,17 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
TextTheme textTheme, TextTheme textTheme,
ValueChanged<double> onChanged, ValueChanged<double> onChanged,
TickerProvider vsync, TickerProvider vsync,
@required TextDirection textDirection,
}) : assert(value != null && value >= 0.0 && value <= 1.0), }) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(textDirection != null),
_value = value, _value = value,
_divisions = divisions, _divisions = divisions,
_activeColor = activeColor, _activeColor = activeColor,
_inactiveColor = inactiveColor, _inactiveColor = inactiveColor,
_thumbOpenAtMin = thumbOpenAtMin, _thumbOpenAtMin = thumbOpenAtMin,
_textTheme = textTheme, _textTheme = textTheme,
_onChanged = onChanged { _onChanged = onChanged,
_textDirection = textDirection {
this.label = label; this.label = label;
final GestureArenaTeam team = new GestureArenaTeam(); final GestureArenaTeam team = new GestureArenaTeam();
_drag = new HorizontalDragGestureRecognizer() _drag = new HorizontalDragGestureRecognizer()
...@@ -415,6 +420,17 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -415,6 +420,17 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
} }
} }
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
return;
_textDirection = value;
// TODO(abarth): Update _labelPainter's text direction.
markNeedsPaint();
}
double get _trackLength => size.width - 2.0 * _kReactionRadius; double get _trackLength => size.width - 2.0 * _kReactionRadius;
Animation<double> _reaction; Animation<double> _reaction;
...@@ -430,8 +446,19 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -430,8 +446,19 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
bool get isInteractive => onChanged != null; bool get isInteractive => onChanged != null;
double _getValueFromVisualPosition(double visualPosition) {
switch (textDirection) {
case TextDirection.rtl:
return 1.0 - visualPosition;
case TextDirection.ltr:
return visualPosition;
}
return null;
}
double _getValueFromGlobalPosition(Offset globalPosition) { double _getValueFromGlobalPosition(Offset globalPosition) {
return (globalToLocal(globalPosition).dx - _kReactionRadius) / _trackLength; final double visualPosition = (globalToLocal(globalPosition).dx - _kReactionRadius) / _trackLength;
return _getValueFromVisualPosition(visualPosition);
} }
double _discretize(double value) { double _discretize(double value) {
...@@ -452,7 +479,15 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -452,7 +479,15 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (isInteractive) { if (isInteractive) {
_currentDragValue += details.primaryDelta / _trackLength; final double valueDelta = details.primaryDelta / _trackLength;
switch (textDirection) {
case TextDirection.rtl:
_currentDragValue -= valueDelta;
break;
case TextDirection.ltr:
_currentDragValue += valueDelta;
break;
}
onChanged(_discretize(_currentDragValue)); onChanged(_discretize(_currentDragValue));
} }
} }
...@@ -523,6 +558,26 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -523,6 +558,26 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
final double trackLength = size.width - 2 * _kReactionRadius; final double trackLength = size.width - 2 * _kReactionRadius;
final bool enabled = isInteractive; final bool enabled = isInteractive;
final double value = _position.value; final double value = _position.value;
final bool thumbAtMin = value == 0.0;
final Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _inactiveColor;
final Paint trackPaint = new Paint()..color = _inactiveColor;
double visualPosition;
Paint leftPaint;
Paint rightPaint;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - value;
leftPaint = trackPaint;
rightPaint = primaryPaint;
break;
case TextDirection.ltr:
visualPosition = value;
leftPaint = primaryPaint;
rightPaint = trackPaint;
break;
}
final double additionalHeightForLabel = _getAdditionalHeightForLabel(label); final double additionalHeightForLabel = _getAdditionalHeightForLabel(label);
final double trackCenter = offset.dy + (size.height - additionalHeightForLabel) / 2.0 + additionalHeightForLabel; final double trackCenter = offset.dy + (size.height - additionalHeightForLabel) / 2.0 + additionalHeightForLabel;
...@@ -530,26 +585,23 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -530,26 +585,23 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
final double trackTop = trackCenter - 1.0; final double trackTop = trackCenter - 1.0;
final double trackBottom = trackCenter + 1.0; final double trackBottom = trackCenter + 1.0;
final double trackRight = trackLeft + trackLength; final double trackRight = trackLeft + trackLength;
final double trackActive = trackLeft + trackLength * value; final double trackActive = trackLeft + trackLength * visualPosition;
final Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _inactiveColor;
final Paint trackPaint = new Paint()..color = _inactiveColor;
final Offset thumbCenter = new Offset(trackActive, trackCenter); final Offset thumbCenter = new Offset(trackActive, trackCenter);
final double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius; final double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
if (enabled) { if (enabled) {
if (value > 0.0) if (visualPosition > 0.0)
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), primaryPaint); canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), leftPaint);
if (value < 1.0) { if (visualPosition < 1.0) {
final bool hasBalloon = _reaction.status != AnimationStatus.dismissed && label != null; final bool hasBalloon = _reaction.status != AnimationStatus.dismissed && label != null;
final double trackActiveDelta = hasBalloon ? 0.0 : thumbRadius - 1.0; final double trackActiveDelta = hasBalloon ? 0.0 : thumbRadius - 1.0;
canvas.drawRect(new Rect.fromLTRB(trackActive + trackActiveDelta, trackTop, trackRight, trackBottom), trackPaint); canvas.drawRect(new Rect.fromLTRB(trackActive + trackActiveDelta, trackTop, trackRight, trackBottom), rightPaint);
} }
} else { } else {
if (value > 0.0) if (visualPosition > 0.0)
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive - _kDisabledThumbRadius - 2, trackBottom), trackPaint); canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive - _kDisabledThumbRadius - 2, trackBottom), trackPaint);
if (value < 1.0) if (visualPosition < 1.0)
canvas.drawRect(new Rect.fromLTRB(trackActive + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint); canvas.drawRect(new Rect.fromLTRB(trackActive + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint);
} }
...@@ -589,7 +641,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -589,7 +641,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
_labelPainter.paint(canvas, labelOffset); _labelPainter.paint(canvas, labelOffset);
return; return;
} else { } else {
final Color reactionBaseColor = value == 0.0 ? _kActiveTrackColor : _activeColor; final Color reactionBaseColor = thumbAtMin ? _kActiveTrackColor : _activeColor;
final Paint reactionPaint = new Paint()..color = reactionBaseColor.withAlpha(kRadialReactionAlpha); final Paint reactionPaint = new Paint()..color = reactionBaseColor.withAlpha(kRadialReactionAlpha);
canvas.drawCircle(thumbCenter, _kReactionRadiusTween.evaluate(_reaction), reactionPaint); canvas.drawCircle(thumbCenter, _kReactionRadiusTween.evaluate(_reaction), reactionPaint);
} }
...@@ -597,7 +649,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -597,7 +649,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
Paint thumbPaint = primaryPaint; Paint thumbPaint = primaryPaint;
double thumbRadiusDelta = 0.0; double thumbRadiusDelta = 0.0;
if (value == 0.0 && thumbOpenAtMin) { if (thumbAtMin && thumbOpenAtMin) {
thumbPaint = trackPaint; thumbPaint = trackPaint;
// This is destructive to trackPaint. // This is destructive to trackPaint.
thumbPaint thumbPaint
......
...@@ -11,12 +11,13 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -11,12 +11,13 @@ import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
void main() { void main() {
testWidgets('Slider does not move when tapped', (WidgetTester tester) async { testWidgets('Slider does not move when tapped (LTR)', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey(); final Key sliderKey = new UniqueKey();
double value = 0.0; double value = 0.0;
await tester.pumpWidget( await tester.pumpWidget(new Directionality(
new StatefulBuilder( textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new Material(
child: new Center( child: new Center(
...@@ -33,7 +34,41 @@ void main() { ...@@ -33,7 +34,41 @@ void main() {
); );
}, },
), ),
); ));
expect(value, equals(0.0));
await tester.tap(find.byKey(sliderKey));
expect(value, equals(0.0));
await tester.pump(); // No animation should start.
// Check the transientCallbackCount before tearing down the widget to ensure
// that no animation is running.
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
});
testWidgets('Slider does not move when tapped (RTL)', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey();
double value = 0.0;
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.rtl,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new CupertinoSlider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
));
expect(value, equals(0.0)); expect(value, equals(0.0));
await tester.tap(find.byKey(sliderKey)); await tester.tap(find.byKey(sliderKey));
...@@ -44,12 +79,14 @@ void main() { ...@@ -44,12 +79,14 @@ void main() {
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
}); });
testWidgets('Slider moves when dragged', (WidgetTester tester) async {
testWidgets('Slider moves when dragged (LTR)', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey(); final Key sliderKey = new UniqueKey();
double value = 0.0; double value = 0.0;
await tester.pumpWidget( await tester.pumpWidget(new Directionality(
new StatefulBuilder( textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new Material(
child: new Center( child: new Center(
...@@ -66,7 +103,7 @@ void main() { ...@@ -66,7 +103,7 @@ void main() {
); );
}, },
), ),
); ));
expect(value, equals(0.0)); expect(value, equals(0.0));
final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey)); final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey));
...@@ -81,15 +118,54 @@ void main() { ...@@ -81,15 +118,54 @@ void main() {
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
}); });
testWidgets('Slider moves when dragged (RTL)', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey();
double value = 0.0;
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.rtl,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new CupertinoSlider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
));
expect(value, equals(0.0));
final Offset bottomRight = tester.getBottomRight(find.byKey(sliderKey));
const double unit = CupertinoThumbPainter.radius;
const double delta = 3.0 * unit;
await tester.dragFrom(bottomRight - const Offset(unit, unit), const Offset(-delta, 0.0));
final Size size = tester.getSize(find.byKey(sliderKey));
expect(value, equals(delta / (size.width - 2.0 * (8.0 + CupertinoThumbPainter.radius))));
await tester.pump(); // No animation should start.
// Check the transientCallbackCount before tearing down the widget to ensure
// that no animation is running.
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
});
testWidgets('Slider Semantics', (WidgetTester tester) async { testWidgets('Slider Semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget( await tester.pumpWidget(new Directionality(
new CupertinoSlider( textDirection: TextDirection.ltr,
child: new CupertinoSlider(
value: 0.5, value: 0.5,
onChanged: (double v) {}, onChanged: (double v) {},
), ),
); ));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
...@@ -105,12 +181,13 @@ void main() { ...@@ -105,12 +181,13 @@ void main() {
)); ));
// Disable slider // Disable slider
await tester.pumpWidget( await tester.pumpWidget(new Directionality(
new CupertinoSlider( textDirection: TextDirection.ltr,
child: new CupertinoSlider(
value: 0.5, value: 0.5,
onChanged: null, onChanged: null,
), ),
); ));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root(), new TestSemantics.root(),
......
...@@ -219,13 +219,16 @@ void main() { ...@@ -219,13 +219,16 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
new SemanticsDebugger( new SemanticsDebugger(
child: new Material( child: new Directionality(
child: new Center( textDirection: TextDirection.ltr,
child: new Slider( child: new Material(
value: value, child: new Center(
onChanged: (double newValue) { child: new Slider(
value = newValue; value: value,
}, onChanged: (double newValue) {
value = newValue;
},
),
), ),
), ),
), ),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment