[Material] Update slider gallery demo, including range slider (#34597)

Update the flutter_gallery Slider demo by aligning the text field in the first slider, adding range slider examples (including custom range thumbs), and separating single slider and range slider examples with tabs.
This commit is contained in:
Anthony 2019-06-24 17:10:11 -04:00 committed by GitHub
parent a276ab3d6d
commit 04bd87794d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 328 additions and 152 deletions

View file

@ -15,19 +15,95 @@ class SliderDemo extends StatefulWidget {
_SliderDemoState createState() => _SliderDemoState();
}
Path _triangle(double size, Offset thumbCenter, {bool invert = false}) {
Path _downTriangle(double size, Offset thumbCenter, { bool invert = false }) {
final Path thumbPath = Path();
final double height = math.sqrt(3.0) / 2.0;
final double halfSide = size / 2.0;
final double centerHeight = size * height / 3.0;
final double halfSize = size / 2.0;
final double sign = invert ? -1.0 : 1.0;
thumbPath.moveTo(thumbCenter.dx - halfSide, thumbCenter.dy + sign * centerHeight);
thumbPath.moveTo(thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx + halfSide, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.close();
return thumbPath;
}
Path _rightTriangle(double size, Offset thumbCenter, { bool invert = false }) {
final Path thumbPath = Path();
final double halfSize = size / 2.0;
final double sign = invert ? -1.0 : 1.0;
thumbPath.moveTo(thumbCenter.dx + halfSize * sign, thumbCenter.dy);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy - size);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy + size);
thumbPath.close();
return thumbPath;
}
Path _upTriangle(double size, Offset thumbCenter) => _downTriangle(size, thumbCenter, invert: true);
Path _leftTriangle(double size, Offset thumbCenter) => _rightTriangle(size, thumbCenter, invert: true);
class _CustomRangeThumbShape extends RangeSliderThumbShape {
static const double _thumbSize = 4.0;
static const double _disabledThumbSize = 3.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
}
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledThumbSize,
end: _thumbSize,
);
@override
void paint(
PaintingContext context,
Offset center, {
@required Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete = false,
bool isEnabled = false,
bool isOnTop,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
Thumb thumb,
}) {
final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
Path thumbPath;
switch (textDirection) {
case TextDirection.rtl:
switch (thumb) {
case Thumb.start:
thumbPath = _rightTriangle(size, center);
break;
case Thumb.end:
thumbPath = _leftTriangle(size, center);
break;
}
break;
case TextDirection.ltr:
switch (thumb) {
case Thumb.start:
thumbPath = _leftTriangle(size, center);
break;
case Thumb.end:
thumbPath = _rightTriangle(size, center);
break;
}
break;
}
canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
}
}
class _CustomThumbShape extends SliderComponentShape {
static const double _thumbSize = 4.0;
static const double _disabledThumbSize = 3.0;
@ -61,7 +137,7 @@ class _CustomThumbShape extends SliderComponentShape {
end: sliderTheme.thumbColor,
);
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
final Path thumbPath = _triangle(size, thumbCenter);
final Path thumbPath = _downTriangle(size, thumbCenter);
canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
}
}
@ -105,11 +181,7 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
);
final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);
final Offset slideUpOffset = Offset(0.0, -slideUpTween.evaluate(activationAnimation));
final Path thumbPath = _triangle(
size,
thumbCenter + slideUpOffset,
invert: true,
);
final Path thumbPath = _upTriangle(size, thumbCenter + slideUpOffset);
final Color paintColor = enableColor.evaluate(enableAnimation).withAlpha((255.0 * activationAnimation.value).round());
canvas.drawPath(
thumbPath,
@ -127,142 +199,243 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
}
class _SliderDemoState extends State<SliderDemo> {
double _value = 25.0;
@override
Widget build(BuildContext context) {
final List<ComponentDemoTabData> demos = <ComponentDemoTabData>[
ComponentDemoTabData(
tabName: 'SINGLE',
description: 'Sliders containing 1 thumb',
demoWidget: _Sliders(),
documentationUrl: 'https://docs.flutter.io/flutter/material/Slider-class.html',
),
ComponentDemoTabData(
tabName: 'RANGE',
description: 'Sliders containing 2 thumbs',
demoWidget: _RangeSliders(),
documentationUrl: 'https://docs.flutter.io/flutter/material/RangeSlider-class.html',
),
];
return TabbedComponentDemoScaffold(
title: 'Sliders',
demos: demos,
isScrollable: false,
showExampleCodeAction: false,
);
}
}
class _Sliders extends StatefulWidget {
@override
_SlidersState createState() => _SlidersState();
}
class _SlidersState extends State<_Sliders> {
double _continuousValue = 25.0;
double _discreteValue = 20.0;
double _discreteCustomValue = 25.0;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Sliders'),
actions: <Widget>[MaterialDemoDocumentationButton(SliderDemo.routeName)],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider.adaptive(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_value = value;
});
},
),
const Text('Continuous'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Slider.adaptive(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_value = value;
});
},
),
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Semantics(
label: 'Editable numerical value',
child: SizedBox(
width: 64,
height: 48,
child: TextField(
textAlign: TextAlign.center,
onSubmitted: (String value) {
final double newValue = double.tryParse(value);
if (newValue != null && newValue != _continuousValue) {
setState(() {
_continuousValue = newValue.clamp(0, 100);
});
}
},
keyboardType: TextInputType.number,
controller: TextEditingController(
text: _continuousValue.toStringAsFixed(0),
),
Semantics(
label: 'Editable numerical value',
child: Container(
width: 48,
height: 48,
child: TextField(
onSubmitted: (String value) {
final double newValue = double.tryParse(value);
if (newValue != null && newValue != _value) {
setState(() {
_value = newValue.clamp(0, 100);
});
}
},
keyboardType: TextInputType.number,
controller: TextEditingController(
text: _value.toStringAsFixed(0),
),
),
),
),
],
),
),
const Text('Continuous with Editable Numerical Value'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Slider.adaptive(value: 0.25, onChanged: null),
Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider.adaptive(
value: _discreteValue,
),
Slider.adaptive(
value: _continuousValue,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_continuousValue = value;
});
},
),
const Text('Continuous with Editable Numerical Value'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Slider.adaptive(value: 0.25, onChanged: null),
Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider.adaptive(
value: _discreteValue,
min: 0.0,
max: 200.0,
divisions: 5,
label: '${_discreteValue.round()}',
onChanged: (double value) {
setState(() {
_discreteValue = value;
});
},
),
const Text('Discrete'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SliderTheme(
data: theme.sliderTheme.copyWith(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: theme.colorScheme.onSurface.withOpacity(0.5),
activeTickMarkColor: theme.colorScheme.onSurface.withOpacity(0.7),
inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7),
overlayColor: theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,
thumbShape: _CustomThumbShape(),
valueIndicatorShape: _CustomValueIndicatorShape(),
valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: theme.colorScheme.onSurface),
),
child: Slider(
value: _discreteCustomValue,
min: 0.0,
max: 200.0,
divisions: 5,
label: '${_discreteValue.round()}',
semanticFormatterCallback: (double value) => value.round().toString(),
label: '${_discreteCustomValue.round()}',
onChanged: (double value) {
setState(() {
_discreteValue = value;
_discreteCustomValue = value;
});
},
),
const Text('Discrete'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SliderTheme(
data: theme.sliderTheme.copyWith(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.black26,
activeTickMarkColor: Colors.white70,
inactiveTickMarkColor: Colors.black,
overlayColor: Colors.black12,
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,
thumbShape: _CustomThumbShape(),
valueIndicatorShape: _CustomValueIndicatorShape(),
valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: Colors.black87),
),
child: Slider(
value: _discreteValue,
min: 0.0,
max: 200.0,
divisions: 5,
semanticFormatterCallback: (double value) => value.round().toString(),
label: '${_discreteValue.round()}',
onChanged: (double value) {
setState(() {
_discreteValue = value;
});
},
),
),
const Text('Discrete with Custom Theme'),
],
),
],
),
),
const Text('Discrete with Custom Theme'),
],
),
],
),
);
}
}
class _RangeSliders extends StatefulWidget {
@override
_RangeSlidersState createState() => _RangeSlidersState();
}
class _RangeSlidersState extends State<_RangeSliders> {
RangeValues _continuousValues = const RangeValues(25.0, 75.0);
RangeValues _discreteValues = const RangeValues(40.0, 120.0);
RangeValues _discreteCustomValues = const RangeValues(40.0, 160.0);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(
values: _continuousValues,
min: 0.0,
max: 100.0,
onChanged: (RangeValues values) {
setState(() {
_continuousValues = values;
});
},
),
const Text('Continuous'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(values: const RangeValues(0.25, 0.75), onChanged: null),
const Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(
values: _discreteValues,
min: 0.0,
max: 200.0,
divisions: 5,
labels: RangeLabels('${_discreteValues.start.round()}', '${_discreteValues.end.round()}'),
onChanged: (RangeValues values) {
setState(() {
_discreteValues = values;
});
},
),
const Text('Discrete'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SliderTheme(
data: SliderThemeData(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.black26,
activeTickMarkColor: Colors.white70,
inactiveTickMarkColor: Colors.black,
overlayColor: Colors.black12,
thumbColor: Colors.deepPurple,
rangeThumbShape: _CustomRangeThumbShape(),
showValueIndicator: ShowValueIndicator.never,
),
child: RangeSlider(
values: _discreteCustomValues,
min: 0.0,
max: 200.0,
divisions: 5,
labels: RangeLabels('${_discreteCustomValues.start.round()}', '${_discreteCustomValues.end.round()}'),
onChanged: (RangeValues values) {
setState(() {
_discreteCustomValues = values;
});
},
),
),
const Text('Discrete with Custom Theme'),
],
),
],
),
);
}
}

View file

@ -46,11 +46,15 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
this.title,
this.demos,
this.actions,
this.isScrollable = true,
this.showExampleCodeAction = true,
});
final List<ComponentDemoTabData> demos;
final String title;
final List<Widget> actions;
final bool isScrollable;
final bool showExampleCodeAction;
void _showExampleCode(BuildContext context) {
final String tag = demos[DefaultTabController.of(context).index].exampleCodeTag;
@ -93,29 +97,28 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
child: Scaffold(
appBar: AppBar(
title: Text(title),
actions: (actions ?? <Widget>[])..addAll(
<Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'),
onPressed: () => _showApiDocumentation(context),
);
},
),
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.code),
tooltip: 'Show example code',
onPressed: () => _showExampleCode(context),
);
},
),
],
),
actions: (actions ?? <Widget>[])..add(
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'),
onPressed: () => _showApiDocumentation(context),
);
},
),
)..addAll(showExampleCodeAction ? <Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.code),
tooltip: 'Show example code',
onPressed: () => _showExampleCode(context),
);
},
),
] : <Widget>[]),
bottom: TabBar(
isScrollable: true,
isScrollable: isScrollable,
tabs: demos.map<Widget>((ComponentDemoTabData data) => Tab(text: data.tabName)).toList(),
),
),