Commit 0c6dc598 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Slider shouldn't be open at min (#7342)

This patch changes the default appearance of Slider to not have the
thumb be an open circle at its minimum position. The `thumbOpenAtMin`
property can enable drawing an open thumb at the min position, which was
the previous behavior.

Fixes #6941
parent c613a85c
......@@ -30,6 +30,7 @@ class _SliderDemoState extends State<SliderDemo> {
value: _value,
min: 0.0,
max: 100.0,
thumbOpenAtMin: true,
onChanged: (double value) {
setState(() {
_value = value;
......@@ -42,7 +43,7 @@ class _SliderDemoState extends State<SliderDemo> {
new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget> [
new Slider(value: 0.25, onChanged: null),
new Slider(value: 0.25, thumbOpenAtMin: true, onChanged: null),
new Text('Disabled'),
]
),
......@@ -55,6 +56,7 @@ class _SliderDemoState extends State<SliderDemo> {
max: 100.0,
divisions: 5,
label: '${_discreteValue.round()}',
thumbOpenAtMin: true,
onChanged: (double value) {
setState(() {
_discreteValue = value;
......
......@@ -89,7 +89,7 @@ class CupertinoSlider extends StatefulWidget {
/// ```
final ValueChanged<double> onChanged;
/// The minium value the user can select.
/// The minimum value the user can select.
///
/// Defaults to 0.0.
final double min;
......
......@@ -57,13 +57,15 @@ class Slider extends StatefulWidget {
this.max: 1.0,
this.divisions,
this.label,
this.activeColor
this.activeColor,
this.thumbOpenAtMin: false,
}) : super(key: key) {
assert(value != null);
assert(min != null);
assert(max != null);
assert(value >= min && value <= max);
assert(divisions == null || divisions > 0);
assert(thumbOpenAtMin != null);
}
/// The currently selected value for this slider.
......@@ -99,7 +101,7 @@ class Slider extends StatefulWidget {
/// ```
final ValueChanged<double> onChanged;
/// The minium value the user can select.
/// The minimum value the user can select.
///
/// Defaults to 0.0.
final double min;
......@@ -126,6 +128,19 @@ class Slider extends StatefulWidget {
/// Defaults to accent color of the current [Theme].
final Color activeColor;
/// Whether the thumb should be an open circle when the slider is at its minimum position.
///
/// When this property is false, the thumb does not change when it the slider
/// reaches its minimum position.
///
/// This property is useful, for example, when the minimum value represents a
/// qualitatively different state. For a slider that controls the volume of
/// a sound, for example, the minimum value represents "no sound at all,"
/// which is qualitatively different from even a very soft sound.
///
/// Defaults to false.
final bool thumbOpenAtMin;
@override
_SliderState createState() => new _SliderState();
}
......@@ -145,6 +160,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
divisions: config.divisions,
label: config.label,
activeColor: config.activeColor ?? theme.accentColor,
thumbOpenAtMin: config.thumbOpenAtMin,
textTheme: theme.accentTextTheme,
onChanged: config.onChanged != null ? _handleChanged : null,
vsync: this,
......@@ -159,6 +175,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.divisions,
this.label,
this.activeColor,
this.thumbOpenAtMin,
this.textTheme,
this.onChanged,
this.vsync,
......@@ -168,20 +185,24 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
final int divisions;
final String label;
final Color activeColor;
final bool thumbOpenAtMin;
final TextTheme textTheme;
final ValueChanged<double> onChanged;
final TickerProvider vsync;
@override
_RenderSlider createRenderObject(BuildContext context) => new _RenderSlider(
value: value,
divisions: divisions,
label: label,
activeColor: activeColor,
textTheme: textTheme,
onChanged: onChanged,
vsync: vsync,
);
_RenderSlider createRenderObject(BuildContext context) {
return new _RenderSlider(
value: value,
divisions: divisions,
label: label,
activeColor: activeColor,
thumbOpenAtMin: thumbOpenAtMin,
textTheme: textTheme,
onChanged: onChanged,
vsync: vsync,
);
}
@override
void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
......@@ -190,10 +211,11 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..divisions = divisions
..label = label
..activeColor = activeColor
..thumbOpenAtMin = thumbOpenAtMin
..textTheme = textTheme
..onChanged = onChanged;
// Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object.
// Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object.
}
}
......@@ -235,12 +257,14 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
int divisions,
String label,
Color activeColor,
bool thumbOpenAtMin,
TextTheme textTheme,
this.onChanged,
TickerProvider vsync,
}) : _value = value,
_divisions = divisions,
_activeColor = activeColor,
_thumbOpenAtMin = thumbOpenAtMin,
_textTheme = textTheme,
super(additionalConstraints: _getAdditionalConstraints(label)) {
assert(value != null && value >= 0.0 && value <= 1.0);
......@@ -317,6 +341,15 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
markNeedsPaint();
}
bool get thumbOpenAtMin => _thumbOpenAtMin;
bool _thumbOpenAtMin;
set thumbOpenAtMin(bool value) {
if (value == _thumbOpenAtMin)
return;
_thumbOpenAtMin = value;
markNeedsPaint();
}
TextTheme get textTheme => _textTheme;
TextTheme _textTheme;
set textTheme(TextTheme value) {
......@@ -464,7 +497,7 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
Paint thumbPaint = primaryPaint;
double thumbRadiusDelta = 0.0;
if (value == 0.0) {
if (value == 0.0 && thumbOpenAtMin) {
thumbPaint = trackPaint;
// This is destructive to trackPaint.
thumbPaint
......
......@@ -5,6 +5,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
enum RadiusType {
Sharp,
Shifting,
......@@ -59,27 +61,6 @@ BorderRadius getBorderRadius(WidgetTester tester, int index) {
return boxDecoration.borderRadius;
}
class TestCanvas implements Canvas {
final List<Invocation> invocations = <Invocation>[];
@override
void noSuchMethod(Invocation invocation) {
invocations.add(invocation);
}
}
class TestPaintingContext implements PaintingContext {
TestPaintingContext(this.canvas);
@override
final Canvas canvas;
@override
void noSuchMethod(Invocation invocation) {
}
}
void main() {
testWidgets('MergeableMaterial empty', (WidgetTester tester) async {
await tester.pumpWidget(
......@@ -223,9 +204,9 @@ void main() {
);
RenderBox box = tester.renderObject(find.byType(MergeableMaterial));
TestCanvas canvas = new TestCanvas();
MockCanvas canvas = new MockCanvas();
box.paint(new TestPaintingContext(canvas), Offset.zero);
box.paint(new MockPaintingContext(canvas), Offset.zero);
final Invocation drawCommand = canvas.invocations.firstWhere((Invocation invocation) {
return invocation.memberName == #drawRRect;
......
......@@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
testWidgets('Slider can move when tapped', (WidgetTester tester) async {
Key sliderKey = new UniqueKey();
......@@ -23,12 +25,12 @@ void main() {
setState(() {
value = newValue;
});
}
)
)
},
),
),
);
}
)
},
),
);
expect(value, equals(0.0));
......@@ -57,12 +59,12 @@ void main() {
setState(() {
value = newValue;
});
}
)
)
},
),
),
);
}
)
},
),
);
expect(value, equals(0.0));
......@@ -82,4 +84,38 @@ void main() {
// Animation complete.
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
});
testWidgets('Slider can draw an open thumb at min',
(WidgetTester tester) async {
Widget buildApp(bool thumbOpenAtMin) {
return new Material(
child: new Center(
child: new Slider(
value: 0.0,
thumbOpenAtMin: thumbOpenAtMin,
onChanged: (double newValue) {},
),
),
);
}
await tester.pumpWidget(buildApp(false));
final RenderBox sliderBox =
tester.firstRenderObject<RenderBox>(find.byType(Slider));
Paint getThumbPaint() {
final MockCanvas canvas = new MockCanvas();
sliderBox.paint(new MockPaintingContext(canvas), Offset.zero);
final Invocation drawCommand =
canvas.invocations.where((Invocation invocation) {
return invocation.memberName == #drawCircle;
}).single;
return drawCommand.positionalArguments[2];
}
expect(getThumbPaint().style, equals(PaintingStyle.fill));
await tester.pumpWidget(buildApp(true));
expect(getThumbPaint().style, equals(PaintingStyle.stroke));
});
}
// Copyright 2016 The Chromium 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 'package:flutter/rendering.dart';
class MockCanvas implements Canvas {
final List<Invocation> invocations = <Invocation>[];
@override
void noSuchMethod(Invocation invocation) {
invocations.add(invocation);
}
}
class MockPaintingContext implements PaintingContext {
MockPaintingContext(this.canvas);
@override
final Canvas canvas;
@override
void noSuchMethod(Invocation invocation) {
}
}
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