Unverified Commit 0572f158 authored by Anthony's avatar Anthony Committed by GitHub

[Material] Adaptive Slider constructor (#30572)

Adds an adaptive constructor for the Material Slider. An adaptive widget is one that renders itself as Material on Android, and Cupertino on iOS. This work is based off of a similar feature on Switches: bbb080b3#diff-fe2bb980c6207699cbf45538fe927afa.

The motivation for this change is that we should provide adaptive constructors for as many widgets as necessary in the Material library. In Material, it is suggested that the slider is an iOS-style slider.
parent 1da7f1b9
......@@ -146,7 +146,7 @@ class _SliderDemoState extends State<SliderDemo> {
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider(
Slider.adaptive(
value: _value,
min: 0.0,
max: 100.0,
......@@ -165,7 +165,7 @@ class _SliderDemoState extends State<SliderDemo> {
Row(
children: <Widget>[
Expanded(
child: Slider(
child: Slider.adaptive(
value: _value,
min: 0.0,
max: 100.0,
......@@ -205,14 +205,14 @@ class _SliderDemoState extends State<SliderDemo> {
Column(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Slider(value: 0.25, onChanged: null),
Slider.adaptive(value: 0.25, onChanged: null),
Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider(
Slider.adaptive(
value: _discreteValue,
min: 0.0,
max: 200.0,
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math' as math;
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
......@@ -30,6 +31,8 @@ import 'theme.dart';
/// * [Slider.semanticFormatterCallback], which shows an example use case.
typedef SemanticFormatterCallback = String Function(double value);
enum _SliderType { material, adaptive }
/// A Material Design slider.
///
/// Used to select from a range of values.
......@@ -89,7 +92,7 @@ typedef SemanticFormatterCallback = String Function(double value);
/// * <https://material.io/design/components/sliders.html>
/// * [MediaQuery], from which the text scale factor is obtained.
class Slider extends StatefulWidget {
/// Creates a material design slider.
/// Creates a Material Design slider.
///
/// The slider itself does not maintain any state. Instead, when the state of
/// the slider changes, the widget calls the [onChanged] callback. Most
......@@ -121,7 +124,37 @@ class Slider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
}) : assert(value != null),
}) : _sliderType = _SliderType.material,
assert(value != null),
assert(min != null),
assert(max != null),
assert(min <= max),
assert(value >= min && value <= max),
assert(divisions == null || divisions > 0),
super(key: key);
/// Creates a [CupertinoSlider] if the target platform is iOS, creates a
/// Material Design slider otherwise.
///
/// If a [CupertinoSlider] is created, the following parameters are
/// ignored: [label], [inactiveColor], [semanticFormatterCallback].
///
/// The target platform is based on the current [Theme]: [ThemeData.platform].
const Slider.adaptive({
Key key,
@required this.value,
@required this.onChanged,
this.onChangeStart,
this.onChangeEnd,
this.min = 0.0,
this.max = 1.0,
this.divisions,
this.label,
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
}) : _sliderType = _SliderType.adaptive,
assert(value != null),
assert(min != null),
assert(max != null),
assert(min <= max),
......@@ -273,6 +306,8 @@ class Slider extends StatefulWidget {
///
/// If null, then the value indicator will not be displayed.
///
/// Ignored if this slider is created with [Slider.adaptive].
///
/// See also:
///
/// * [SliderComponentShape] for how to create a custom value indicator
......@@ -300,6 +335,8 @@ class Slider extends StatefulWidget {
///
/// Using a [SliderTheme] gives much more fine-grained control over the
/// appearance of various components of the slider.
///
/// Ignored if this slider is created with [Slider.adaptive].
final Color inactiveColor;
/// The callback used to create a semantic value from a slider value.
......@@ -331,8 +368,12 @@ class Slider extends StatefulWidget {
/// )
/// ```
/// {@end-tool}
///
/// Ignored if this slider is created with [Slider.adaptive]
final SemanticFormatterCallback semanticFormatterCallback;
final _SliderType _sliderType ;
@override
_SliderState createState() => _SliderState();
......@@ -441,6 +482,27 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMediaQuery(context));
switch (widget._sliderType) {
case _SliderType.material:
return _buildMaterialSlider(context);
case _SliderType.adaptive: {
final ThemeData theme = Theme.of(context);
assert(theme.platform != null);
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return _buildMaterialSlider(context);
case TargetPlatform.iOS:
return _buildCupertinoSlider(context);
}
}
}
assert(false);
return null;
}
Widget _buildMaterialSlider(BuildContext context) {
final ThemeData theme = Theme.of(context);
SliderThemeData sliderTheme = SliderTheme.of(context);
......@@ -488,6 +550,25 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
semanticFormatterCallback: widget.semanticFormatterCallback,
);
}
Widget _buildCupertinoSlider(BuildContext context) {
// The render box of a slider has a fixed height but takes up the available
// width. Wrapping the [CupertinoSlider] in this manner will help maintain
// the same size.
return SizedBox(
width: double.infinity,
child: CupertinoSlider(
value: widget.value,
onChanged: widget.onChanged,
onChangeStart: widget.onChangeStart,
onChangeEnd: widget.onChangeEnd,
min: widget.min,
max: widget.max,
divisions: widget.divisions,
activeColor: widget.activeColor,
),
);
}
}
class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
......
......@@ -4,6 +4,7 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
......@@ -1436,4 +1437,52 @@ void main() {
expect(await tester.pumpAndSettle(const Duration(milliseconds: 100)), equals(1));
await gesture.up();
});
testWidgets('Slider.adaptive', (WidgetTester tester) async {
double value = 0.5;
Widget buildFrame(TargetPlatform platform) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Slider.adaptive(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.iOS));
expect(find.byType(Slider), findsOneWidget);
expect(find.byType(CupertinoSlider), findsOneWidget);
expect(value, 0.5);
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CupertinoSlider)));
// Drag to the right end of the track.
await gesture.moveBy(const Offset(600.0, 0.0));
expect(value, 1.0);
value = 0.5;
await tester.pumpWidget(buildFrame(TargetPlatform.android));
await tester.pumpAndSettle(); // Finish the theme change animation.
expect(find.byType(Slider), findsOneWidget);
expect(find.byType(CupertinoSlider), findsNothing);
expect(value, 0.5);
gesture = await tester.startGesture(tester.getCenter(find.byType(Slider)));
// Drag to the right end of the track.
await gesture.moveBy(const Offset(600.0, 0.0));
expect(value, 1.0);
});
}
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